@workos-inc/widgets 1.16.0-next.1785954585478 → 1.16.0
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/CHANGELOG.md +7 -1
- package/dist/cjs/lib/elements.cjs +2 -2
- package/dist/cjs/lib/elements.cjs.map +1 -1
- package/dist/cjs/lib/pipes-admin.cjs +7 -8
- package/dist/cjs/lib/pipes-admin.cjs.map +1 -1
- package/dist/cjs/lib/pipes.cjs +462 -133
- package/dist/cjs/lib/pipes.cjs.map +1 -1
- package/dist/cjs/lib/redacted-secret.cjs +33 -0
- package/dist/cjs/lib/redacted-secret.cjs.map +1 -0
- package/dist/cjs/lib/redacted-secret.d.cts +6 -0
- package/dist/esm/lib/elements.js +2 -2
- package/dist/esm/lib/elements.js.map +1 -1
- package/dist/esm/lib/pipes-admin.js +2 -3
- package/dist/esm/lib/pipes-admin.js.map +1 -1
- package/dist/esm/lib/pipes.js +465 -135
- package/dist/esm/lib/pipes.js.map +1 -1
- package/dist/esm/lib/redacted-secret.d.ts +6 -0
- package/dist/esm/lib/redacted-secret.js +8 -0
- package/dist/esm/lib/redacted-secret.js.map +1 -0
- package/package.json +1 -1
package/dist/esm/lib/pipes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Callout, Card, Flex, Text } from "@radix-ui/themes";
|
|
4
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import * as CardList from "./card-list.js";
|
|
6
6
|
import { DataIntegrationIcon } from "./data-integration-icon.js";
|
|
7
7
|
import { ProviderIcon } from "./provider-icon.js";
|
|
@@ -29,7 +29,8 @@ import {
|
|
|
29
29
|
useAuthorizeDataIntegrationHook,
|
|
30
30
|
useDeleteDataInstallation,
|
|
31
31
|
useSettings,
|
|
32
|
-
useUpsertDataIntegrationApiKey
|
|
32
|
+
useUpsertDataIntegrationApiKey,
|
|
33
|
+
useUpsertDataIntegrationClientCredentials
|
|
33
34
|
} from "../api/endpoint.js";
|
|
34
35
|
import { useQueryClient } from "@tanstack/react-query";
|
|
35
36
|
import { GenericError } from "./generic-error.js";
|
|
@@ -37,8 +38,14 @@ import { getDomProps } from "./utils.js";
|
|
|
37
38
|
import { Translation } from "./i18n/translation.js";
|
|
38
39
|
import { useTranslation } from "./i18n/use-translation.js";
|
|
39
40
|
import { ApiError } from "../api/errors.js";
|
|
41
|
+
import { buildRedactedSecret, isRedactedSecret } from "./redacted-secret.js";
|
|
40
42
|
const HANDOFF_INTERVAL_MS = 100;
|
|
41
43
|
const HANDOFF_MAX_ATTEMPTS = 100;
|
|
44
|
+
const CONNECT_AUTH_METHODS = [
|
|
45
|
+
"oauth",
|
|
46
|
+
"client_credentials",
|
|
47
|
+
"api_key"
|
|
48
|
+
];
|
|
42
49
|
const useOpenDataIntegrationAuthorizeUrl = (integration) => {
|
|
43
50
|
const authorizeDataIntegration = useAuthorizeDataIntegrationHook();
|
|
44
51
|
const settings = useSettings();
|
|
@@ -127,6 +134,140 @@ function getUnpinnedInstallationFields(integration) {
|
|
|
127
134
|
(field) => field.scope === "installation" && !integration.config?.[field.key]
|
|
128
135
|
).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
129
136
|
}
|
|
137
|
+
function getInitialConfigValues(fields, storedConfig) {
|
|
138
|
+
const values = {};
|
|
139
|
+
for (const field of fields) {
|
|
140
|
+
const stored = storedConfig[field.key];
|
|
141
|
+
if (typeof stored === "string" || typeof stored === "number" || typeof stored === "boolean") {
|
|
142
|
+
values[field.key] = String(stored);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return values;
|
|
146
|
+
}
|
|
147
|
+
function useConfigFieldsForm(fields, initialValues = {}) {
|
|
148
|
+
const [values, setValues] = useState(initialValues);
|
|
149
|
+
const [errors, setErrors] = useState({});
|
|
150
|
+
const translate = useTranslation();
|
|
151
|
+
const requiredMessage = translate({
|
|
152
|
+
defaultMessage: "This field is required",
|
|
153
|
+
id: "B2HvfC",
|
|
154
|
+
description: "Validation error shown when a required configuration field is left empty"
|
|
155
|
+
});
|
|
156
|
+
const patternMessage = translate({
|
|
157
|
+
defaultMessage: "Enter a valid value.",
|
|
158
|
+
id: "2XWaGz",
|
|
159
|
+
description: "Validation error shown when a configuration field value does not match the expected format"
|
|
160
|
+
});
|
|
161
|
+
const setValue = (key, value) => {
|
|
162
|
+
setValues((prev) => ({ ...prev, [key]: value }));
|
|
163
|
+
setErrors((prev) => prev[key] ? { ...prev, [key]: null } : prev);
|
|
164
|
+
};
|
|
165
|
+
const reset = () => {
|
|
166
|
+
setValues(initialValues);
|
|
167
|
+
setErrors({});
|
|
168
|
+
};
|
|
169
|
+
const validate = () => {
|
|
170
|
+
const nextErrors = {};
|
|
171
|
+
let valid = true;
|
|
172
|
+
for (const field of fields) {
|
|
173
|
+
const value = (values[field.key] ?? "").trim();
|
|
174
|
+
if (field.required && !value) {
|
|
175
|
+
nextErrors[field.key] = requiredMessage;
|
|
176
|
+
valid = false;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (value && field.pattern) {
|
|
180
|
+
let matches = true;
|
|
181
|
+
try {
|
|
182
|
+
matches = new RegExp(field.pattern).test(value);
|
|
183
|
+
} catch {
|
|
184
|
+
matches = true;
|
|
185
|
+
}
|
|
186
|
+
if (!matches) {
|
|
187
|
+
nextErrors[field.key] = patternMessage;
|
|
188
|
+
valid = false;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
nextErrors[field.key] = null;
|
|
193
|
+
}
|
|
194
|
+
setErrors(nextErrors);
|
|
195
|
+
return valid;
|
|
196
|
+
};
|
|
197
|
+
const buildConfig = () => {
|
|
198
|
+
const config = {};
|
|
199
|
+
for (const field of fields) {
|
|
200
|
+
const value = (values[field.key] ?? "").trim();
|
|
201
|
+
if (value) {
|
|
202
|
+
config[field.key] = value;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return config;
|
|
206
|
+
};
|
|
207
|
+
const buildConfigPayload = (storedConfig) => {
|
|
208
|
+
const payload = { ...storedConfig };
|
|
209
|
+
for (const field of fields) {
|
|
210
|
+
const value = (values[field.key] ?? "").trim();
|
|
211
|
+
if (value) {
|
|
212
|
+
payload[field.key] = value;
|
|
213
|
+
} else {
|
|
214
|
+
delete payload[field.key];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return payload;
|
|
218
|
+
};
|
|
219
|
+
const hasMissingRequired = fields.some(
|
|
220
|
+
(field) => field.required && !(values[field.key] ?? "").trim()
|
|
221
|
+
);
|
|
222
|
+
const isDirty = fields.some(
|
|
223
|
+
(field) => (values[field.key] ?? "") !== (initialValues[field.key] ?? "")
|
|
224
|
+
);
|
|
225
|
+
return {
|
|
226
|
+
values,
|
|
227
|
+
errors,
|
|
228
|
+
setValue,
|
|
229
|
+
reset,
|
|
230
|
+
validate,
|
|
231
|
+
buildConfig,
|
|
232
|
+
buildConfigPayload,
|
|
233
|
+
hasMissingRequired,
|
|
234
|
+
isDirty
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function ConfigFieldInputs({
|
|
238
|
+
idPrefix,
|
|
239
|
+
fields,
|
|
240
|
+
values,
|
|
241
|
+
errors,
|
|
242
|
+
onChange
|
|
243
|
+
}) {
|
|
244
|
+
return fields.map((field) => {
|
|
245
|
+
const fieldId = `${idPrefix}-${field.key}`;
|
|
246
|
+
const errorId = `${fieldId}-error`;
|
|
247
|
+
const descriptionId = `${fieldId}-description`;
|
|
248
|
+
const error = errors[field.key];
|
|
249
|
+
const Control = field.secret ? PasswordField : TextField;
|
|
250
|
+
const describedBy = [field.description ? descriptionId : null, error ? errorId : null].filter(Boolean).join(" ") || void 0;
|
|
251
|
+
return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "1", children: [
|
|
252
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: fieldId, children: field.label }),
|
|
253
|
+
field.description ? /* @__PURE__ */ jsx(Text, { color: "gray", size: "2", id: descriptionId, mb: "1", children: field.description }) : null,
|
|
254
|
+
/* @__PURE__ */ jsx(
|
|
255
|
+
Control,
|
|
256
|
+
{
|
|
257
|
+
id: fieldId,
|
|
258
|
+
value: values[field.key] ?? "",
|
|
259
|
+
placeholder: field.example,
|
|
260
|
+
autoComplete: "off",
|
|
261
|
+
required: field.required || void 0,
|
|
262
|
+
"aria-invalid": !!error || void 0,
|
|
263
|
+
"aria-describedby": describedBy,
|
|
264
|
+
onChange: (event) => onChange(field.key, event.target.value)
|
|
265
|
+
}
|
|
266
|
+
),
|
|
267
|
+
error ? /* @__PURE__ */ jsx(Text, { color: "red", size: "2", id: errorId, children: error }) : null
|
|
268
|
+
] }, field.key);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
130
271
|
function LogoSeparator() {
|
|
131
272
|
return /* @__PURE__ */ jsx(
|
|
132
273
|
DotsHorizontalIcon,
|
|
@@ -239,22 +380,11 @@ function ConnectDialog({
|
|
|
239
380
|
}) {
|
|
240
381
|
const initialStep = fields.length > 0 ? "fields" : "review";
|
|
241
382
|
const [step, setStep] = useState(initialStep);
|
|
242
|
-
const [values, setValues] = useState({});
|
|
243
|
-
const [errors, setErrors] = useState({});
|
|
244
383
|
const [submitError, setSubmitError] = useState(null);
|
|
245
384
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
385
|
+
const configForm = useConfigFieldsForm(fields);
|
|
246
386
|
const eventHandler = useOpenDataIntegrationAuthorizeUrl(integration);
|
|
247
387
|
const translate = useTranslation();
|
|
248
|
-
const requiredMessage = translate({
|
|
249
|
-
defaultMessage: "This field is required",
|
|
250
|
-
id: "B2HvfC",
|
|
251
|
-
description: "Validation error shown when a required configuration field is left empty"
|
|
252
|
-
});
|
|
253
|
-
const patternMessage = translate({
|
|
254
|
-
defaultMessage: "Enter a valid value.",
|
|
255
|
-
id: "2XWaGz",
|
|
256
|
-
description: "Validation error shown when a configuration field value does not match the expected format"
|
|
257
|
-
});
|
|
258
388
|
const genericConnectError = translate({
|
|
259
389
|
defaultMessage: "We couldn't start the connection. Please try again.",
|
|
260
390
|
id: "zGw1ma",
|
|
@@ -262,57 +392,15 @@ function ConnectDialog({
|
|
|
262
392
|
});
|
|
263
393
|
const reset = () => {
|
|
264
394
|
setStep(initialStep);
|
|
265
|
-
|
|
266
|
-
setErrors({});
|
|
395
|
+
configForm.reset();
|
|
267
396
|
setSubmitError(null);
|
|
268
397
|
setIsSubmitting(false);
|
|
269
398
|
};
|
|
270
|
-
const validateFields = () => {
|
|
271
|
-
const nextErrors = {};
|
|
272
|
-
let valid = true;
|
|
273
|
-
for (const field of fields) {
|
|
274
|
-
const value = (values[field.key] ?? "").trim();
|
|
275
|
-
if (field.required && !value) {
|
|
276
|
-
nextErrors[field.key] = requiredMessage;
|
|
277
|
-
valid = false;
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
if (value && field.pattern) {
|
|
281
|
-
let matches = true;
|
|
282
|
-
try {
|
|
283
|
-
matches = new RegExp(field.pattern).test(value);
|
|
284
|
-
} catch {
|
|
285
|
-
matches = true;
|
|
286
|
-
}
|
|
287
|
-
if (!matches) {
|
|
288
|
-
nextErrors[field.key] = patternMessage;
|
|
289
|
-
valid = false;
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
nextErrors[field.key] = null;
|
|
294
|
-
}
|
|
295
|
-
setErrors(nextErrors);
|
|
296
|
-
return valid;
|
|
297
|
-
};
|
|
298
|
-
const buildConfig = () => {
|
|
299
|
-
const config = {};
|
|
300
|
-
for (const field of fields) {
|
|
301
|
-
const value = (values[field.key] ?? "").trim();
|
|
302
|
-
if (value) {
|
|
303
|
-
config[field.key] = value;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return config;
|
|
307
|
-
};
|
|
308
|
-
const hasMissingRequired = fields.some(
|
|
309
|
-
(field) => field.required && !(values[field.key] ?? "").trim()
|
|
310
|
-
);
|
|
311
399
|
const handleConnect = async () => {
|
|
312
400
|
setSubmitError(null);
|
|
313
401
|
setIsSubmitting(true);
|
|
314
402
|
try {
|
|
315
|
-
await eventHandler(buildConfig());
|
|
403
|
+
await eventHandler(configForm.buildConfig());
|
|
316
404
|
onOpenChange(false);
|
|
317
405
|
} catch (error) {
|
|
318
406
|
setSubmitError(
|
|
@@ -335,46 +423,16 @@ function ConnectDialog({
|
|
|
335
423
|
children: /* @__PURE__ */ jsxs(Dialog.Content, { maxWidth: "430px", children: [
|
|
336
424
|
/* @__PURE__ */ jsx(ConnectDialogHeader, { integration }),
|
|
337
425
|
step === "fields" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
338
|
-
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Flex, { direction: "column", gap: "4", children:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "1", children: [
|
|
349
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: fieldId, children: field.label }),
|
|
350
|
-
field.description ? /* @__PURE__ */ jsx(Text, { color: "gray", size: "2", id: descriptionId, mb: "1", children: field.description }) : null,
|
|
351
|
-
/* @__PURE__ */ jsx(
|
|
352
|
-
Control,
|
|
353
|
-
{
|
|
354
|
-
id: fieldId,
|
|
355
|
-
value: values[field.key] ?? "",
|
|
356
|
-
placeholder: field.example,
|
|
357
|
-
autoComplete: "off",
|
|
358
|
-
required: field.required || void 0,
|
|
359
|
-
"aria-invalid": !!error || void 0,
|
|
360
|
-
"aria-describedby": describedBy,
|
|
361
|
-
onChange: (event) => {
|
|
362
|
-
setValues((prev) => ({
|
|
363
|
-
...prev,
|
|
364
|
-
[field.key]: event.target.value
|
|
365
|
-
}));
|
|
366
|
-
if (error) {
|
|
367
|
-
setErrors((prev) => ({
|
|
368
|
-
...prev,
|
|
369
|
-
[field.key]: null
|
|
370
|
-
}));
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
),
|
|
375
|
-
error ? /* @__PURE__ */ jsx(Text, { color: "red", size: "2", id: errorId, children: error }) : null
|
|
376
|
-
] }, field.key);
|
|
377
|
-
}) }) }),
|
|
426
|
+
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Flex, { direction: "column", gap: "4", children: /* @__PURE__ */ jsx(
|
|
427
|
+
ConfigFieldInputs,
|
|
428
|
+
{
|
|
429
|
+
idPrefix: "pipes-config",
|
|
430
|
+
fields,
|
|
431
|
+
values: configForm.values,
|
|
432
|
+
errors: configForm.errors,
|
|
433
|
+
onChange: configForm.setValue
|
|
434
|
+
}
|
|
435
|
+
) }) }),
|
|
378
436
|
/* @__PURE__ */ jsxs(Flex, { justify: "end", gap: "3", mt: "5", children: [
|
|
379
437
|
/* @__PURE__ */ jsx(Dialog.Close, { children: /* @__PURE__ */ jsx(Button, { variant: "secondary", children: /* @__PURE__ */ jsx(
|
|
380
438
|
Translation,
|
|
@@ -388,9 +446,9 @@ function ConnectDialog({
|
|
|
388
446
|
Button,
|
|
389
447
|
{
|
|
390
448
|
type: "button",
|
|
391
|
-
disabled: hasMissingRequired,
|
|
449
|
+
disabled: configForm.hasMissingRequired,
|
|
392
450
|
onClick: () => {
|
|
393
|
-
if (
|
|
451
|
+
if (configForm.validate()) {
|
|
394
452
|
setStep("review");
|
|
395
453
|
}
|
|
396
454
|
},
|
|
@@ -654,11 +712,247 @@ function ApiKeyConnectButton({
|
|
|
654
712
|
) })
|
|
655
713
|
] });
|
|
656
714
|
}
|
|
657
|
-
function
|
|
715
|
+
function ClientCredentialsDialog({
|
|
716
|
+
integration,
|
|
717
|
+
fields,
|
|
718
|
+
open,
|
|
719
|
+
onOpenChange
|
|
720
|
+
}) {
|
|
721
|
+
const queryClient = useQueryClient();
|
|
722
|
+
const installation = integration.installation;
|
|
723
|
+
const isUpdate = installation?.auth_method === "client_credentials";
|
|
724
|
+
const storedConfig = useMemo(
|
|
725
|
+
() => isUpdate ? installation?.config ?? {} : {},
|
|
726
|
+
[isUpdate, installation?.config]
|
|
727
|
+
);
|
|
728
|
+
const initialConfigValues = useMemo(
|
|
729
|
+
() => getInitialConfigValues(fields, storedConfig),
|
|
730
|
+
[fields, storedConfig]
|
|
731
|
+
);
|
|
732
|
+
const redactedSecret = buildRedactedSecret(
|
|
733
|
+
isUpdate ? installation?.client_secret_last_4 : null
|
|
734
|
+
);
|
|
735
|
+
const [clientId, setClientId] = useState(
|
|
736
|
+
(isUpdate ? installation?.client_id : null) ?? ""
|
|
737
|
+
);
|
|
738
|
+
const [clientSecret, setClientSecret] = useState(redactedSecret);
|
|
739
|
+
const configForm = useConfigFieldsForm(fields, initialConfigValues);
|
|
740
|
+
const translate = useTranslation();
|
|
741
|
+
const clientIdPlaceholder = translate({
|
|
742
|
+
defaultMessage: "Enter your client ID",
|
|
743
|
+
id: "5YTAFo",
|
|
744
|
+
description: "Placeholder for the client credentials client ID input"
|
|
745
|
+
});
|
|
746
|
+
const clientSecretPlaceholder = translate({
|
|
747
|
+
defaultMessage: "Enter your client secret",
|
|
748
|
+
id: "53f0B7",
|
|
749
|
+
description: "Placeholder for the client credentials client secret input"
|
|
750
|
+
});
|
|
751
|
+
const secretMissing = !clientSecret.trim() || isRedactedSecret(clientSecret);
|
|
752
|
+
const {
|
|
753
|
+
mutate: upsertClientCredentials,
|
|
754
|
+
error,
|
|
755
|
+
isPending
|
|
756
|
+
} = useUpsertDataIntegrationClientCredentials({
|
|
757
|
+
mutation: {
|
|
758
|
+
onSuccess: () => {
|
|
759
|
+
onOpenChange(false);
|
|
760
|
+
queryClient.invalidateQueries({
|
|
761
|
+
queryKey: getMyDataIntegrationsQueryKey()
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
const errorMessage = error instanceof ApiError && error.message ? error.message : null;
|
|
767
|
+
const handleSubmit = () => {
|
|
768
|
+
if (!configForm.validate()) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const config = configForm.isDirty ? configForm.buildConfigPayload(storedConfig) : void 0;
|
|
772
|
+
upsertClientCredentials({
|
|
773
|
+
slug: integration.slug,
|
|
774
|
+
data: {
|
|
775
|
+
client_id: clientId.trim(),
|
|
776
|
+
client_secret: clientSecret.trim(),
|
|
777
|
+
...config ? { config } : {}
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
};
|
|
781
|
+
return /* @__PURE__ */ jsx(Dialog.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs(Dialog.Content, { maxWidth: "430px", children: [
|
|
782
|
+
/* @__PURE__ */ jsx(Dialog.Title, { size: "3", weight: "bold", children: isUpdate ? /* @__PURE__ */ jsx(
|
|
783
|
+
Translation,
|
|
784
|
+
{
|
|
785
|
+
defaultMessage: "Update client credentials for {integrationName}",
|
|
786
|
+
id: "/D85zF",
|
|
787
|
+
description: "Dialog title for updating stored client credentials",
|
|
788
|
+
values: { integrationName: integration.name }
|
|
789
|
+
}
|
|
790
|
+
) : /* @__PURE__ */ jsx(
|
|
791
|
+
Translation,
|
|
792
|
+
{
|
|
793
|
+
defaultMessage: "Connect {integrationName} with client credentials",
|
|
794
|
+
id: "x+7tw3",
|
|
795
|
+
description: "Dialog title for connecting with client credentials",
|
|
796
|
+
values: { integrationName: integration.name }
|
|
797
|
+
}
|
|
798
|
+
) }),
|
|
799
|
+
error && /* @__PURE__ */ jsx(Callout.Root, { my: "3", color: "red", role: "alert", children: /* @__PURE__ */ jsx(Callout.Text, { children: errorMessage ?? /* @__PURE__ */ jsx(
|
|
800
|
+
Translation,
|
|
801
|
+
{
|
|
802
|
+
defaultMessage: "An error occurred. Please check the value and try again.",
|
|
803
|
+
id: "bYLbLL",
|
|
804
|
+
description: "Generic error message for API key submission failure"
|
|
805
|
+
}
|
|
806
|
+
) }) }),
|
|
807
|
+
/* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "4", mt: "2", children: [
|
|
808
|
+
/* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "1", children: [
|
|
809
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "pipes-client-credentials-client-id", children: /* @__PURE__ */ jsx(
|
|
810
|
+
Translation,
|
|
811
|
+
{
|
|
812
|
+
defaultMessage: "Client ID",
|
|
813
|
+
id: "/Cv5P1",
|
|
814
|
+
description: "Label for the client credentials client ID input"
|
|
815
|
+
}
|
|
816
|
+
) }),
|
|
817
|
+
/* @__PURE__ */ jsx(
|
|
818
|
+
TextField,
|
|
819
|
+
{
|
|
820
|
+
id: "pipes-client-credentials-client-id",
|
|
821
|
+
value: clientId,
|
|
822
|
+
autoComplete: "off",
|
|
823
|
+
placeholder: clientIdPlaceholder,
|
|
824
|
+
onChange: (event) => setClientId(event.target.value)
|
|
825
|
+
}
|
|
826
|
+
)
|
|
827
|
+
] }),
|
|
828
|
+
/* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "1", children: [
|
|
829
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "pipes-client-credentials-client-secret", children: /* @__PURE__ */ jsx(
|
|
830
|
+
Translation,
|
|
831
|
+
{
|
|
832
|
+
defaultMessage: "Client secret",
|
|
833
|
+
id: "uScBDj",
|
|
834
|
+
description: "Label for the client credentials client secret input"
|
|
835
|
+
}
|
|
836
|
+
) }),
|
|
837
|
+
isUpdate && /* @__PURE__ */ jsx(
|
|
838
|
+
Text,
|
|
839
|
+
{
|
|
840
|
+
color: "gray",
|
|
841
|
+
size: "2",
|
|
842
|
+
id: "pipes-client-credentials-client-secret-description",
|
|
843
|
+
mb: "1",
|
|
844
|
+
children: /* @__PURE__ */ jsx(
|
|
845
|
+
Translation,
|
|
846
|
+
{
|
|
847
|
+
defaultMessage: "Enter the client secret again to save any changes.",
|
|
848
|
+
id: "ZrNPCF",
|
|
849
|
+
description: "Help text explaining the client secret must be re-entered to save an update"
|
|
850
|
+
}
|
|
851
|
+
)
|
|
852
|
+
}
|
|
853
|
+
),
|
|
854
|
+
/* @__PURE__ */ jsx(
|
|
855
|
+
PasswordField,
|
|
856
|
+
{
|
|
857
|
+
id: "pipes-client-credentials-client-secret",
|
|
858
|
+
value: clientSecret,
|
|
859
|
+
autoComplete: "off",
|
|
860
|
+
type: isRedactedSecret(clientSecret) ? "text" : void 0,
|
|
861
|
+
placeholder: clientSecretPlaceholder,
|
|
862
|
+
"aria-describedby": isUpdate ? "pipes-client-credentials-client-secret-description" : void 0,
|
|
863
|
+
onChange: (event) => setClientSecret(event.target.value),
|
|
864
|
+
onFocus: () => {
|
|
865
|
+
if (isRedactedSecret(clientSecret)) {
|
|
866
|
+
setClientSecret("");
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
onBlur: () => {
|
|
870
|
+
if (clientSecret === "") {
|
|
871
|
+
setClientSecret(redactedSecret);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
)
|
|
876
|
+
] }),
|
|
877
|
+
/* @__PURE__ */ jsx(
|
|
878
|
+
ConfigFieldInputs,
|
|
879
|
+
{
|
|
880
|
+
idPrefix: "pipes-client-credentials-config",
|
|
881
|
+
fields,
|
|
882
|
+
values: configForm.values,
|
|
883
|
+
errors: configForm.errors,
|
|
884
|
+
onChange: configForm.setValue
|
|
885
|
+
}
|
|
886
|
+
)
|
|
887
|
+
] }),
|
|
888
|
+
/* @__PURE__ */ jsxs(Flex, { justify: "end", gap: "3", mt: "5", children: [
|
|
889
|
+
/* @__PURE__ */ jsx(Dialog.Close, { children: /* @__PURE__ */ jsx(Button, { variant: "secondary", children: /* @__PURE__ */ jsx(
|
|
890
|
+
Translation,
|
|
891
|
+
{
|
|
892
|
+
defaultMessage: "Cancel",
|
|
893
|
+
id: "hHNj31",
|
|
894
|
+
description: "Cancel button text"
|
|
895
|
+
}
|
|
896
|
+
) }) }),
|
|
897
|
+
/* @__PURE__ */ jsx(
|
|
898
|
+
Button,
|
|
899
|
+
{
|
|
900
|
+
type: "button",
|
|
901
|
+
disabled: isPending || !clientId.trim() || secretMissing || configForm.hasMissingRequired,
|
|
902
|
+
loading: isPending,
|
|
903
|
+
onClick: handleSubmit,
|
|
904
|
+
children: isUpdate ? /* @__PURE__ */ jsx(
|
|
905
|
+
Translation,
|
|
906
|
+
{
|
|
907
|
+
defaultMessage: "Save",
|
|
908
|
+
id: "RT8KNi",
|
|
909
|
+
description: "Save button text"
|
|
910
|
+
}
|
|
911
|
+
) : /* @__PURE__ */ jsx(
|
|
912
|
+
Translation,
|
|
913
|
+
{
|
|
914
|
+
defaultMessage: "Connect",
|
|
915
|
+
id: "quvdFp",
|
|
916
|
+
description: "Button to connect an integration"
|
|
917
|
+
}
|
|
918
|
+
)
|
|
919
|
+
}
|
|
920
|
+
)
|
|
921
|
+
] })
|
|
922
|
+
] }) });
|
|
923
|
+
}
|
|
924
|
+
function ClientCredentialsConnectButton({
|
|
658
925
|
integration
|
|
926
|
+
}) {
|
|
927
|
+
const [open, setOpen] = useState(false);
|
|
928
|
+
const fields = getUnpinnedInstallationFields(integration);
|
|
929
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
930
|
+
open && /* @__PURE__ */ jsx(
|
|
931
|
+
ClientCredentialsDialog,
|
|
932
|
+
{
|
|
933
|
+
integration,
|
|
934
|
+
fields,
|
|
935
|
+
open,
|
|
936
|
+
onOpenChange: setOpen
|
|
937
|
+
}
|
|
938
|
+
),
|
|
939
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setOpen(true), children: /* @__PURE__ */ jsx(
|
|
940
|
+
Translation,
|
|
941
|
+
{
|
|
942
|
+
defaultMessage: "Connect",
|
|
943
|
+
id: "quvdFp",
|
|
944
|
+
description: "Button to connect an integration"
|
|
945
|
+
}
|
|
946
|
+
) })
|
|
947
|
+
] });
|
|
948
|
+
}
|
|
949
|
+
function MultiMethodConnectButton({
|
|
950
|
+
integration,
|
|
951
|
+
methods
|
|
659
952
|
}) {
|
|
660
953
|
const [apiKeyOpen, setApiKeyOpen] = useState(false);
|
|
661
954
|
const [oauthOpen, setOauthOpen] = useState(false);
|
|
955
|
+
const [clientCredentialsOpen, setClientCredentialsOpen] = useState(false);
|
|
662
956
|
const fields = getUnpinnedInstallationFields(integration);
|
|
663
957
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
664
958
|
apiKeyOpen && /* @__PURE__ */ jsx(
|
|
@@ -669,7 +963,16 @@ function DualMethodConnectButton({
|
|
|
669
963
|
onOpenChange: setApiKeyOpen
|
|
670
964
|
}
|
|
671
965
|
),
|
|
672
|
-
/* @__PURE__ */ jsx(
|
|
966
|
+
clientCredentialsOpen && /* @__PURE__ */ jsx(
|
|
967
|
+
ClientCredentialsDialog,
|
|
968
|
+
{
|
|
969
|
+
integration,
|
|
970
|
+
fields,
|
|
971
|
+
open: clientCredentialsOpen,
|
|
972
|
+
onOpenChange: setClientCredentialsOpen
|
|
973
|
+
}
|
|
974
|
+
),
|
|
975
|
+
methods.includes("oauth") && /* @__PURE__ */ jsx(
|
|
673
976
|
ConnectDialog,
|
|
674
977
|
{
|
|
675
978
|
integration,
|
|
@@ -688,7 +991,7 @@ function DualMethodConnectButton({
|
|
|
688
991
|
}
|
|
689
992
|
) }) }),
|
|
690
993
|
/* @__PURE__ */ jsxs(DropdownMenu.Content, { align: "end", children: [
|
|
691
|
-
/* @__PURE__ */ jsx(DropdownMenu.Item, { onClick: () => setOauthOpen(true), children: /* @__PURE__ */ jsxs(Flex, { gap: "4", width: "100%", justify: "between", align: "center", children: [
|
|
994
|
+
methods.includes("oauth") && /* @__PURE__ */ jsx(DropdownMenu.Item, { onClick: () => setOauthOpen(true), children: /* @__PURE__ */ jsxs(Flex, { gap: "4", width: "100%", justify: "between", align: "center", children: [
|
|
692
995
|
/* @__PURE__ */ jsx(
|
|
693
996
|
Translation,
|
|
694
997
|
{
|
|
@@ -700,7 +1003,15 @@ function DualMethodConnectButton({
|
|
|
700
1003
|
),
|
|
701
1004
|
/* @__PURE__ */ jsx(ExternalLinkIcon, { "aria-hidden": true })
|
|
702
1005
|
] }) }),
|
|
703
|
-
/* @__PURE__ */ jsx(DropdownMenu.Item, { onClick: () =>
|
|
1006
|
+
methods.includes("client_credentials") && /* @__PURE__ */ jsx(DropdownMenu.Item, { onClick: () => setClientCredentialsOpen(true), children: /* @__PURE__ */ jsx(
|
|
1007
|
+
Translation,
|
|
1008
|
+
{
|
|
1009
|
+
defaultMessage: "Enter client credentials",
|
|
1010
|
+
id: "Y/87Vt",
|
|
1011
|
+
description: "Menu option to connect via client credentials"
|
|
1012
|
+
}
|
|
1013
|
+
) }),
|
|
1014
|
+
methods.includes("api_key") && /* @__PURE__ */ jsx(DropdownMenu.Item, { onClick: () => setApiKeyOpen(true), children: /* @__PURE__ */ jsx(
|
|
704
1015
|
Translation,
|
|
705
1016
|
{
|
|
706
1017
|
defaultMessage: "Enter API key",
|
|
@@ -715,6 +1026,7 @@ function DualMethodConnectButton({
|
|
|
715
1026
|
function ProviderStatus({ integration }) {
|
|
716
1027
|
const [disconnectOpen, setDisconnectOpen] = useState(false);
|
|
717
1028
|
const [apiKeyOpen, setApiKeyOpen] = useState(false);
|
|
1029
|
+
const [clientCredentialsOpen, setClientCredentialsOpen] = useState(false);
|
|
718
1030
|
const [reauthConfigOpen, setReauthConfigOpen] = useState(false);
|
|
719
1031
|
const authorizeEventHandler = useOpenDataIntegrationAuthorizeUrl(integration);
|
|
720
1032
|
const translate = useTranslation();
|
|
@@ -727,6 +1039,7 @@ function ProviderStatus({ integration }) {
|
|
|
727
1039
|
if (integration.installation) {
|
|
728
1040
|
const connected = integration.installation.state === "connected";
|
|
729
1041
|
const isApiKeyInstallation = integration.installation.auth_method === "api_key";
|
|
1042
|
+
const isClientCredentialsInstallation = integration.installation.auth_method === "client_credentials";
|
|
730
1043
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
731
1044
|
/* @__PURE__ */ jsx(
|
|
732
1045
|
DisconnectAccountDialog,
|
|
@@ -745,7 +1058,16 @@ function ProviderStatus({ integration }) {
|
|
|
745
1058
|
onOpenChange: setApiKeyOpen
|
|
746
1059
|
}
|
|
747
1060
|
),
|
|
748
|
-
|
|
1061
|
+
clientCredentialsOpen && /* @__PURE__ */ jsx(
|
|
1062
|
+
ClientCredentialsDialog,
|
|
1063
|
+
{
|
|
1064
|
+
integration,
|
|
1065
|
+
fields: unpinnedFields,
|
|
1066
|
+
open: clientCredentialsOpen,
|
|
1067
|
+
onOpenChange: setClientCredentialsOpen
|
|
1068
|
+
}
|
|
1069
|
+
),
|
|
1070
|
+
unpinnedFields.length > 0 && !isClientCredentialsInstallation && /* @__PURE__ */ jsx(
|
|
749
1071
|
ConnectDialog,
|
|
750
1072
|
{
|
|
751
1073
|
integration,
|
|
@@ -770,7 +1092,40 @@ function ProviderStatus({ integration }) {
|
|
|
770
1092
|
description: "Status text for integration needing reauthorization"
|
|
771
1093
|
}
|
|
772
1094
|
) }),
|
|
773
|
-
|
|
1095
|
+
isClientCredentialsInstallation ? /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
|
|
1096
|
+
/* @__PURE__ */ jsx(DropdownMenu.Trigger, { children: /* @__PURE__ */ jsx(IconButton, { title: pipeActionsTitle, children: /* @__PURE__ */ jsx(DotsHorizontalIcon, {}) }) }),
|
|
1097
|
+
/* @__PURE__ */ jsxs(DropdownMenu.Content, { align: "end", children: [
|
|
1098
|
+
/* @__PURE__ */ jsx(
|
|
1099
|
+
DropdownMenu.Item,
|
|
1100
|
+
{
|
|
1101
|
+
onClick: () => setClientCredentialsOpen(true),
|
|
1102
|
+
children: /* @__PURE__ */ jsx(
|
|
1103
|
+
Translation,
|
|
1104
|
+
{
|
|
1105
|
+
defaultMessage: "Update client credentials",
|
|
1106
|
+
id: "daHKWE",
|
|
1107
|
+
description: "Menu option to update stored client credentials"
|
|
1108
|
+
}
|
|
1109
|
+
)
|
|
1110
|
+
}
|
|
1111
|
+
),
|
|
1112
|
+
/* @__PURE__ */ jsx(
|
|
1113
|
+
DropdownMenu.Item,
|
|
1114
|
+
{
|
|
1115
|
+
variant: "destructive",
|
|
1116
|
+
onClick: () => setDisconnectOpen(true),
|
|
1117
|
+
children: /* @__PURE__ */ jsx(
|
|
1118
|
+
Translation,
|
|
1119
|
+
{
|
|
1120
|
+
defaultMessage: "Disconnect account",
|
|
1121
|
+
id: "esZQrd",
|
|
1122
|
+
description: "Menu option to disconnect an account"
|
|
1123
|
+
}
|
|
1124
|
+
)
|
|
1125
|
+
}
|
|
1126
|
+
)
|
|
1127
|
+
] })
|
|
1128
|
+
] }) : connected && isApiKeyInstallation ? /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
|
|
774
1129
|
/* @__PURE__ */ jsx(DropdownMenu.Trigger, { children: /* @__PURE__ */ jsx(IconButton, { title: pipeActionsTitle, children: /* @__PURE__ */ jsx(DotsHorizontalIcon, {}) }) }),
|
|
775
1130
|
/* @__PURE__ */ jsxs(DropdownMenu.Content, { align: "end", children: [
|
|
776
1131
|
/* @__PURE__ */ jsx(DropdownMenu.Item, { onClick: () => setApiKeyOpen(true), children: /* @__PURE__ */ jsx(
|
|
@@ -882,34 +1237,9 @@ function ProviderStatus({ integration }) {
|
|
|
882
1237
|
] });
|
|
883
1238
|
}
|
|
884
1239
|
const authMethods = integration.authMethods ?? [];
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "4", children: [
|
|
889
|
-
/* @__PURE__ */ jsx(Status, { state: "neutral", children: /* @__PURE__ */ jsx(
|
|
890
|
-
Translation,
|
|
891
|
-
{
|
|
892
|
-
defaultMessage: "Not connected",
|
|
893
|
-
id: "J7bTZa",
|
|
894
|
-
description: "Status text for an integration that is not connected"
|
|
895
|
-
}
|
|
896
|
-
) }),
|
|
897
|
-
/* @__PURE__ */ jsx(DualMethodConnectButton, { integration })
|
|
898
|
-
] });
|
|
899
|
-
}
|
|
900
|
-
if (supportsApiKey) {
|
|
901
|
-
return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "4", children: [
|
|
902
|
-
/* @__PURE__ */ jsx(Status, { state: "neutral", children: /* @__PURE__ */ jsx(
|
|
903
|
-
Translation,
|
|
904
|
-
{
|
|
905
|
-
defaultMessage: "Not connected",
|
|
906
|
-
id: "J7bTZa",
|
|
907
|
-
description: "Status text for an integration that is not connected"
|
|
908
|
-
}
|
|
909
|
-
) }),
|
|
910
|
-
/* @__PURE__ */ jsx(ApiKeyConnectButton, { integration })
|
|
911
|
-
] });
|
|
912
|
-
}
|
|
1240
|
+
const methods = CONNECT_AUTH_METHODS.filter(
|
|
1241
|
+
(method) => authMethods.includes(method)
|
|
1242
|
+
);
|
|
913
1243
|
return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "4", children: [
|
|
914
1244
|
/* @__PURE__ */ jsx(Status, { state: "neutral", children: /* @__PURE__ */ jsx(
|
|
915
1245
|
Translation,
|
|
@@ -919,7 +1249,7 @@ function ProviderStatus({ integration }) {
|
|
|
919
1249
|
description: "Status text for an integration that is not connected"
|
|
920
1250
|
}
|
|
921
1251
|
) }),
|
|
922
|
-
/* @__PURE__ */ jsx(ConnectIntegrationButton, { integration })
|
|
1252
|
+
methods.length > 1 ? /* @__PURE__ */ jsx(MultiMethodConnectButton, { integration, methods }) : methods.length === 1 && methods[0] === "api_key" ? /* @__PURE__ */ jsx(ApiKeyConnectButton, { integration }) : methods.length === 1 && methods[0] === "client_credentials" ? /* @__PURE__ */ jsx(ClientCredentialsConnectButton, { integration }) : /* @__PURE__ */ jsx(ConnectIntegrationButton, { integration })
|
|
923
1253
|
] });
|
|
924
1254
|
}
|
|
925
1255
|
function DisconnectAccountDialog({
|