@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.
@@ -51,8 +51,14 @@ var import_utils = require("./utils.js");
51
51
  var import_translation = require("./i18n/translation.js");
52
52
  var import_use_translation = require("./i18n/use-translation.js");
53
53
  var import_errors = require("../api/errors.js");
54
+ var import_redacted_secret = require("./redacted-secret.js");
54
55
  const HANDOFF_INTERVAL_MS = 100;
55
56
  const HANDOFF_MAX_ATTEMPTS = 100;
57
+ const CONNECT_AUTH_METHODS = [
58
+ "oauth",
59
+ "client_credentials",
60
+ "api_key"
61
+ ];
56
62
  const useOpenDataIntegrationAuthorizeUrl = (integration) => {
57
63
  const authorizeDataIntegration = (0, import_endpoint.useAuthorizeDataIntegrationHook)();
58
64
  const settings = (0, import_endpoint.useSettings)();
@@ -141,6 +147,140 @@ function getUnpinnedInstallationFields(integration) {
141
147
  (field) => field.scope === "installation" && !integration.config?.[field.key]
142
148
  ).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
143
149
  }
150
+ function getInitialConfigValues(fields, storedConfig) {
151
+ const values = {};
152
+ for (const field of fields) {
153
+ const stored = storedConfig[field.key];
154
+ if (typeof stored === "string" || typeof stored === "number" || typeof stored === "boolean") {
155
+ values[field.key] = String(stored);
156
+ }
157
+ }
158
+ return values;
159
+ }
160
+ function useConfigFieldsForm(fields, initialValues = {}) {
161
+ const [values, setValues] = (0, import_react.useState)(initialValues);
162
+ const [errors, setErrors] = (0, import_react.useState)({});
163
+ const translate = (0, import_use_translation.useTranslation)();
164
+ const requiredMessage = translate({
165
+ defaultMessage: "This field is required",
166
+ id: "B2HvfC",
167
+ description: "Validation error shown when a required configuration field is left empty"
168
+ });
169
+ const patternMessage = translate({
170
+ defaultMessage: "Enter a valid value.",
171
+ id: "2XWaGz",
172
+ description: "Validation error shown when a configuration field value does not match the expected format"
173
+ });
174
+ const setValue = (key, value) => {
175
+ setValues((prev) => ({ ...prev, [key]: value }));
176
+ setErrors((prev) => prev[key] ? { ...prev, [key]: null } : prev);
177
+ };
178
+ const reset = () => {
179
+ setValues(initialValues);
180
+ setErrors({});
181
+ };
182
+ const validate = () => {
183
+ const nextErrors = {};
184
+ let valid = true;
185
+ for (const field of fields) {
186
+ const value = (values[field.key] ?? "").trim();
187
+ if (field.required && !value) {
188
+ nextErrors[field.key] = requiredMessage;
189
+ valid = false;
190
+ continue;
191
+ }
192
+ if (value && field.pattern) {
193
+ let matches = true;
194
+ try {
195
+ matches = new RegExp(field.pattern).test(value);
196
+ } catch {
197
+ matches = true;
198
+ }
199
+ if (!matches) {
200
+ nextErrors[field.key] = patternMessage;
201
+ valid = false;
202
+ continue;
203
+ }
204
+ }
205
+ nextErrors[field.key] = null;
206
+ }
207
+ setErrors(nextErrors);
208
+ return valid;
209
+ };
210
+ const buildConfig = () => {
211
+ const config = {};
212
+ for (const field of fields) {
213
+ const value = (values[field.key] ?? "").trim();
214
+ if (value) {
215
+ config[field.key] = value;
216
+ }
217
+ }
218
+ return config;
219
+ };
220
+ const buildConfigPayload = (storedConfig) => {
221
+ const payload = { ...storedConfig };
222
+ for (const field of fields) {
223
+ const value = (values[field.key] ?? "").trim();
224
+ if (value) {
225
+ payload[field.key] = value;
226
+ } else {
227
+ delete payload[field.key];
228
+ }
229
+ }
230
+ return payload;
231
+ };
232
+ const hasMissingRequired = fields.some(
233
+ (field) => field.required && !(values[field.key] ?? "").trim()
234
+ );
235
+ const isDirty = fields.some(
236
+ (field) => (values[field.key] ?? "") !== (initialValues[field.key] ?? "")
237
+ );
238
+ return {
239
+ values,
240
+ errors,
241
+ setValue,
242
+ reset,
243
+ validate,
244
+ buildConfig,
245
+ buildConfigPayload,
246
+ hasMissingRequired,
247
+ isDirty
248
+ };
249
+ }
250
+ function ConfigFieldInputs({
251
+ idPrefix,
252
+ fields,
253
+ values,
254
+ errors,
255
+ onChange
256
+ }) {
257
+ return fields.map((field) => {
258
+ const fieldId = `${idPrefix}-${field.key}`;
259
+ const errorId = `${fieldId}-error`;
260
+ const descriptionId = `${fieldId}-description`;
261
+ const error = errors[field.key];
262
+ const Control = field.secret ? import_elements.PasswordField : import_elements.TextField;
263
+ const describedBy = [field.description ? descriptionId : null, error ? errorId : null].filter(Boolean).join(" ") || void 0;
264
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { direction: "column", gap: "1", children: [
265
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Label, { htmlFor: fieldId, children: field.label }),
266
+ field.description ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Text, { color: "gray", size: "2", id: descriptionId, mb: "1", children: field.description }) : null,
267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
268
+ Control,
269
+ {
270
+ id: fieldId,
271
+ value: values[field.key] ?? "",
272
+ placeholder: field.example,
273
+ autoComplete: "off",
274
+ required: field.required || void 0,
275
+ "aria-invalid": !!error || void 0,
276
+ "aria-describedby": describedBy,
277
+ onChange: (event) => onChange(field.key, event.target.value)
278
+ }
279
+ ),
280
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Text, { color: "red", size: "2", id: errorId, children: error }) : null
281
+ ] }, field.key);
282
+ });
283
+ }
144
284
  function LogoSeparator() {
145
285
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
146
286
  import_react_icons.DotsHorizontalIcon,
@@ -253,22 +393,11 @@ function ConnectDialog({
253
393
  }) {
254
394
  const initialStep = fields.length > 0 ? "fields" : "review";
255
395
  const [step, setStep] = (0, import_react.useState)(initialStep);
256
- const [values, setValues] = (0, import_react.useState)({});
257
- const [errors, setErrors] = (0, import_react.useState)({});
258
396
  const [submitError, setSubmitError] = (0, import_react.useState)(null);
259
397
  const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
398
+ const configForm = useConfigFieldsForm(fields);
260
399
  const eventHandler = useOpenDataIntegrationAuthorizeUrl(integration);
261
400
  const translate = (0, import_use_translation.useTranslation)();
262
- const requiredMessage = translate({
263
- defaultMessage: "This field is required",
264
- id: "B2HvfC",
265
- description: "Validation error shown when a required configuration field is left empty"
266
- });
267
- const patternMessage = translate({
268
- defaultMessage: "Enter a valid value.",
269
- id: "2XWaGz",
270
- description: "Validation error shown when a configuration field value does not match the expected format"
271
- });
272
401
  const genericConnectError = translate({
273
402
  defaultMessage: "We couldn't start the connection. Please try again.",
274
403
  id: "zGw1ma",
@@ -276,57 +405,15 @@ function ConnectDialog({
276
405
  });
277
406
  const reset = () => {
278
407
  setStep(initialStep);
279
- setValues({});
280
- setErrors({});
408
+ configForm.reset();
281
409
  setSubmitError(null);
282
410
  setIsSubmitting(false);
283
411
  };
284
- const validateFields = () => {
285
- const nextErrors = {};
286
- let valid = true;
287
- for (const field of fields) {
288
- const value = (values[field.key] ?? "").trim();
289
- if (field.required && !value) {
290
- nextErrors[field.key] = requiredMessage;
291
- valid = false;
292
- continue;
293
- }
294
- if (value && field.pattern) {
295
- let matches = true;
296
- try {
297
- matches = new RegExp(field.pattern).test(value);
298
- } catch {
299
- matches = true;
300
- }
301
- if (!matches) {
302
- nextErrors[field.key] = patternMessage;
303
- valid = false;
304
- continue;
305
- }
306
- }
307
- nextErrors[field.key] = null;
308
- }
309
- setErrors(nextErrors);
310
- return valid;
311
- };
312
- const buildConfig = () => {
313
- const config = {};
314
- for (const field of fields) {
315
- const value = (values[field.key] ?? "").trim();
316
- if (value) {
317
- config[field.key] = value;
318
- }
319
- }
320
- return config;
321
- };
322
- const hasMissingRequired = fields.some(
323
- (field) => field.required && !(values[field.key] ?? "").trim()
324
- );
325
412
  const handleConnect = async () => {
326
413
  setSubmitError(null);
327
414
  setIsSubmitting(true);
328
415
  try {
329
- await eventHandler(buildConfig());
416
+ await eventHandler(configForm.buildConfig());
330
417
  onOpenChange(false);
331
418
  } catch (error) {
332
419
  setSubmitError(
@@ -349,46 +436,16 @@ function ConnectDialog({
349
436
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_elements.Dialog.Content, { maxWidth: "430px", children: [
350
437
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectDialogHeader, { integration }),
351
438
  step === "fields" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
352
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Flex, { direction: "column", gap: "4", children: fields.map((field) => {
353
- const fieldId = `pipes-config-${field.key}`;
354
- const errorId = `${fieldId}-error`;
355
- const descriptionId = `${fieldId}-description`;
356
- const error = errors[field.key];
357
- const Control = field.secret ? import_elements.PasswordField : import_elements.TextField;
358
- const describedBy = [
359
- field.description ? descriptionId : null,
360
- error ? errorId : null
361
- ].filter(Boolean).join(" ") || void 0;
362
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { direction: "column", gap: "1", children: [
363
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Label, { htmlFor: fieldId, children: field.label }),
364
- field.description ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Text, { color: "gray", size: "2", id: descriptionId, mb: "1", children: field.description }) : null,
365
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
366
- Control,
367
- {
368
- id: fieldId,
369
- value: values[field.key] ?? "",
370
- placeholder: field.example,
371
- autoComplete: "off",
372
- required: field.required || void 0,
373
- "aria-invalid": !!error || void 0,
374
- "aria-describedby": describedBy,
375
- onChange: (event) => {
376
- setValues((prev) => ({
377
- ...prev,
378
- [field.key]: event.target.value
379
- }));
380
- if (error) {
381
- setErrors((prev) => ({
382
- ...prev,
383
- [field.key]: null
384
- }));
385
- }
386
- }
387
- }
388
- ),
389
- error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Text, { color: "red", size: "2", id: errorId, children: error }) : null
390
- ] }, field.key);
391
- }) }) }),
439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Flex, { direction: "column", gap: "4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
440
+ ConfigFieldInputs,
441
+ {
442
+ idPrefix: "pipes-config",
443
+ fields,
444
+ values: configForm.values,
445
+ errors: configForm.errors,
446
+ onChange: configForm.setValue
447
+ }
448
+ ) }) }),
392
449
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { justify: "end", gap: "3", mt: "5", children: [
393
450
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Dialog.Close, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Button, { variant: "secondary", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
394
451
  import_translation.Translation,
@@ -402,9 +459,9 @@ function ConnectDialog({
402
459
  import_elements.Button,
403
460
  {
404
461
  type: "button",
405
- disabled: hasMissingRequired,
462
+ disabled: configForm.hasMissingRequired,
406
463
  onClick: () => {
407
- if (validateFields()) {
464
+ if (configForm.validate()) {
408
465
  setStep("review");
409
466
  }
410
467
  },
@@ -668,11 +725,247 @@ function ApiKeyConnectButton({
668
725
  ) })
669
726
  ] });
670
727
  }
671
- function DualMethodConnectButton({
728
+ function ClientCredentialsDialog({
729
+ integration,
730
+ fields,
731
+ open,
732
+ onOpenChange
733
+ }) {
734
+ const queryClient = (0, import_react_query.useQueryClient)();
735
+ const installation = integration.installation;
736
+ const isUpdate = installation?.auth_method === "client_credentials";
737
+ const storedConfig = (0, import_react.useMemo)(
738
+ () => isUpdate ? installation?.config ?? {} : {},
739
+ [isUpdate, installation?.config]
740
+ );
741
+ const initialConfigValues = (0, import_react.useMemo)(
742
+ () => getInitialConfigValues(fields, storedConfig),
743
+ [fields, storedConfig]
744
+ );
745
+ const redactedSecret = (0, import_redacted_secret.buildRedactedSecret)(
746
+ isUpdate ? installation?.client_secret_last_4 : null
747
+ );
748
+ const [clientId, setClientId] = (0, import_react.useState)(
749
+ (isUpdate ? installation?.client_id : null) ?? ""
750
+ );
751
+ const [clientSecret, setClientSecret] = (0, import_react.useState)(redactedSecret);
752
+ const configForm = useConfigFieldsForm(fields, initialConfigValues);
753
+ const translate = (0, import_use_translation.useTranslation)();
754
+ const clientIdPlaceholder = translate({
755
+ defaultMessage: "Enter your client ID",
756
+ id: "5YTAFo",
757
+ description: "Placeholder for the client credentials client ID input"
758
+ });
759
+ const clientSecretPlaceholder = translate({
760
+ defaultMessage: "Enter your client secret",
761
+ id: "53f0B7",
762
+ description: "Placeholder for the client credentials client secret input"
763
+ });
764
+ const secretMissing = !clientSecret.trim() || (0, import_redacted_secret.isRedactedSecret)(clientSecret);
765
+ const {
766
+ mutate: upsertClientCredentials,
767
+ error,
768
+ isPending
769
+ } = (0, import_endpoint.useUpsertDataIntegrationClientCredentials)({
770
+ mutation: {
771
+ onSuccess: () => {
772
+ onOpenChange(false);
773
+ queryClient.invalidateQueries({
774
+ queryKey: (0, import_endpoint.getMyDataIntegrationsQueryKey)()
775
+ });
776
+ }
777
+ }
778
+ });
779
+ const errorMessage = error instanceof import_errors.ApiError && error.message ? error.message : null;
780
+ const handleSubmit = () => {
781
+ if (!configForm.validate()) {
782
+ return;
783
+ }
784
+ const config = configForm.isDirty ? configForm.buildConfigPayload(storedConfig) : void 0;
785
+ upsertClientCredentials({
786
+ slug: integration.slug,
787
+ data: {
788
+ client_id: clientId.trim(),
789
+ client_secret: clientSecret.trim(),
790
+ ...config ? { config } : {}
791
+ }
792
+ });
793
+ };
794
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Dialog.Root, { open, onOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_elements.Dialog.Content, { maxWidth: "430px", children: [
795
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Dialog.Title, { size: "3", weight: "bold", children: isUpdate ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
796
+ import_translation.Translation,
797
+ {
798
+ defaultMessage: "Update client credentials for {integrationName}",
799
+ id: "/D85zF",
800
+ description: "Dialog title for updating stored client credentials",
801
+ values: { integrationName: integration.name }
802
+ }
803
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
804
+ import_translation.Translation,
805
+ {
806
+ defaultMessage: "Connect {integrationName} with client credentials",
807
+ id: "x+7tw3",
808
+ description: "Dialog title for connecting with client credentials",
809
+ values: { integrationName: integration.name }
810
+ }
811
+ ) }),
812
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Callout.Root, { my: "3", color: "red", role: "alert", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_themes.Callout.Text, { children: errorMessage ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
813
+ import_translation.Translation,
814
+ {
815
+ defaultMessage: "An error occurred. Please check the value and try again.",
816
+ id: "bYLbLL",
817
+ description: "Generic error message for API key submission failure"
818
+ }
819
+ ) }) }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { direction: "column", gap: "4", mt: "2", children: [
821
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { direction: "column", gap: "1", children: [
822
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Label, { htmlFor: "pipes-client-credentials-client-id", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
823
+ import_translation.Translation,
824
+ {
825
+ defaultMessage: "Client ID",
826
+ id: "/Cv5P1",
827
+ description: "Label for the client credentials client ID input"
828
+ }
829
+ ) }),
830
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
831
+ import_elements.TextField,
832
+ {
833
+ id: "pipes-client-credentials-client-id",
834
+ value: clientId,
835
+ autoComplete: "off",
836
+ placeholder: clientIdPlaceholder,
837
+ onChange: (event) => setClientId(event.target.value)
838
+ }
839
+ )
840
+ ] }),
841
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { direction: "column", gap: "1", children: [
842
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Label, { htmlFor: "pipes-client-credentials-client-secret", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
843
+ import_translation.Translation,
844
+ {
845
+ defaultMessage: "Client secret",
846
+ id: "uScBDj",
847
+ description: "Label for the client credentials client secret input"
848
+ }
849
+ ) }),
850
+ isUpdate && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
851
+ import_themes.Text,
852
+ {
853
+ color: "gray",
854
+ size: "2",
855
+ id: "pipes-client-credentials-client-secret-description",
856
+ mb: "1",
857
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
858
+ import_translation.Translation,
859
+ {
860
+ defaultMessage: "Enter the client secret again to save any changes.",
861
+ id: "ZrNPCF",
862
+ description: "Help text explaining the client secret must be re-entered to save an update"
863
+ }
864
+ )
865
+ }
866
+ ),
867
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
868
+ import_elements.PasswordField,
869
+ {
870
+ id: "pipes-client-credentials-client-secret",
871
+ value: clientSecret,
872
+ autoComplete: "off",
873
+ type: (0, import_redacted_secret.isRedactedSecret)(clientSecret) ? "text" : void 0,
874
+ placeholder: clientSecretPlaceholder,
875
+ "aria-describedby": isUpdate ? "pipes-client-credentials-client-secret-description" : void 0,
876
+ onChange: (event) => setClientSecret(event.target.value),
877
+ onFocus: () => {
878
+ if ((0, import_redacted_secret.isRedactedSecret)(clientSecret)) {
879
+ setClientSecret("");
880
+ }
881
+ },
882
+ onBlur: () => {
883
+ if (clientSecret === "") {
884
+ setClientSecret(redactedSecret);
885
+ }
886
+ }
887
+ }
888
+ )
889
+ ] }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
891
+ ConfigFieldInputs,
892
+ {
893
+ idPrefix: "pipes-client-credentials-config",
894
+ fields,
895
+ values: configForm.values,
896
+ errors: configForm.errors,
897
+ onChange: configForm.setValue
898
+ }
899
+ )
900
+ ] }),
901
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { justify: "end", gap: "3", mt: "5", children: [
902
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Dialog.Close, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Button, { variant: "secondary", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
903
+ import_translation.Translation,
904
+ {
905
+ defaultMessage: "Cancel",
906
+ id: "hHNj31",
907
+ description: "Cancel button text"
908
+ }
909
+ ) }) }),
910
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
911
+ import_elements.Button,
912
+ {
913
+ type: "button",
914
+ disabled: isPending || !clientId.trim() || secretMissing || configForm.hasMissingRequired,
915
+ loading: isPending,
916
+ onClick: handleSubmit,
917
+ children: isUpdate ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
918
+ import_translation.Translation,
919
+ {
920
+ defaultMessage: "Save",
921
+ id: "RT8KNi",
922
+ description: "Save button text"
923
+ }
924
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
925
+ import_translation.Translation,
926
+ {
927
+ defaultMessage: "Connect",
928
+ id: "quvdFp",
929
+ description: "Button to connect an integration"
930
+ }
931
+ )
932
+ }
933
+ )
934
+ ] })
935
+ ] }) });
936
+ }
937
+ function ClientCredentialsConnectButton({
672
938
  integration
939
+ }) {
940
+ const [open, setOpen] = (0, import_react.useState)(false);
941
+ const fields = getUnpinnedInstallationFields(integration);
942
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
943
+ open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
944
+ ClientCredentialsDialog,
945
+ {
946
+ integration,
947
+ fields,
948
+ open,
949
+ onOpenChange: setOpen
950
+ }
951
+ ),
952
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.Button, { variant: "secondary", onClick: () => setOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
953
+ import_translation.Translation,
954
+ {
955
+ defaultMessage: "Connect",
956
+ id: "quvdFp",
957
+ description: "Button to connect an integration"
958
+ }
959
+ ) })
960
+ ] });
961
+ }
962
+ function MultiMethodConnectButton({
963
+ integration,
964
+ methods
673
965
  }) {
674
966
  const [apiKeyOpen, setApiKeyOpen] = (0, import_react.useState)(false);
675
967
  const [oauthOpen, setOauthOpen] = (0, import_react.useState)(false);
968
+ const [clientCredentialsOpen, setClientCredentialsOpen] = (0, import_react.useState)(false);
676
969
  const fields = getUnpinnedInstallationFields(integration);
677
970
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
678
971
  apiKeyOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -683,7 +976,16 @@ function DualMethodConnectButton({
683
976
  onOpenChange: setApiKeyOpen
684
977
  }
685
978
  ),
686
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
979
+ clientCredentialsOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
980
+ ClientCredentialsDialog,
981
+ {
982
+ integration,
983
+ fields,
984
+ open: clientCredentialsOpen,
985
+ onOpenChange: setClientCredentialsOpen
986
+ }
987
+ ),
988
+ methods.includes("oauth") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
687
989
  ConnectDialog,
688
990
  {
689
991
  integration,
@@ -702,7 +1004,7 @@ function DualMethodConnectButton({
702
1004
  }
703
1005
  ) }) }),
704
1006
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_elements.DropdownMenu.Content, { align: "end", children: [
705
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.DropdownMenu.Item, { onClick: () => setOauthOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { gap: "4", width: "100%", justify: "between", align: "center", children: [
1007
+ methods.includes("oauth") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.DropdownMenu.Item, { onClick: () => setOauthOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { gap: "4", width: "100%", justify: "between", align: "center", children: [
706
1008
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
707
1009
  import_translation.Translation,
708
1010
  {
@@ -714,7 +1016,15 @@ function DualMethodConnectButton({
714
1016
  ),
715
1017
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_icons.ExternalLinkIcon, { "aria-hidden": true })
716
1018
  ] }) }),
717
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.DropdownMenu.Item, { onClick: () => setApiKeyOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1019
+ methods.includes("client_credentials") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.DropdownMenu.Item, { onClick: () => setClientCredentialsOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1020
+ import_translation.Translation,
1021
+ {
1022
+ defaultMessage: "Enter client credentials",
1023
+ id: "Y/87Vt",
1024
+ description: "Menu option to connect via client credentials"
1025
+ }
1026
+ ) }),
1027
+ methods.includes("api_key") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.DropdownMenu.Item, { onClick: () => setApiKeyOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
718
1028
  import_translation.Translation,
719
1029
  {
720
1030
  defaultMessage: "Enter API key",
@@ -729,6 +1039,7 @@ function DualMethodConnectButton({
729
1039
  function ProviderStatus({ integration }) {
730
1040
  const [disconnectOpen, setDisconnectOpen] = (0, import_react.useState)(false);
731
1041
  const [apiKeyOpen, setApiKeyOpen] = (0, import_react.useState)(false);
1042
+ const [clientCredentialsOpen, setClientCredentialsOpen] = (0, import_react.useState)(false);
732
1043
  const [reauthConfigOpen, setReauthConfigOpen] = (0, import_react.useState)(false);
733
1044
  const authorizeEventHandler = useOpenDataIntegrationAuthorizeUrl(integration);
734
1045
  const translate = (0, import_use_translation.useTranslation)();
@@ -741,6 +1052,7 @@ function ProviderStatus({ integration }) {
741
1052
  if (integration.installation) {
742
1053
  const connected = integration.installation.state === "connected";
743
1054
  const isApiKeyInstallation = integration.installation.auth_method === "api_key";
1055
+ const isClientCredentialsInstallation = integration.installation.auth_method === "client_credentials";
744
1056
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
745
1057
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
746
1058
  DisconnectAccountDialog,
@@ -759,7 +1071,16 @@ function ProviderStatus({ integration }) {
759
1071
  onOpenChange: setApiKeyOpen
760
1072
  }
761
1073
  ),
762
- unpinnedFields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1074
+ clientCredentialsOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1075
+ ClientCredentialsDialog,
1076
+ {
1077
+ integration,
1078
+ fields: unpinnedFields,
1079
+ open: clientCredentialsOpen,
1080
+ onOpenChange: setClientCredentialsOpen
1081
+ }
1082
+ ),
1083
+ unpinnedFields.length > 0 && !isClientCredentialsInstallation && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
763
1084
  ConnectDialog,
764
1085
  {
765
1086
  integration,
@@ -784,7 +1105,40 @@ function ProviderStatus({ integration }) {
784
1105
  description: "Status text for integration needing reauthorization"
785
1106
  }
786
1107
  ) }),
787
- connected && isApiKeyInstallation ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_elements.DropdownMenu.Root, { children: [
1108
+ isClientCredentialsInstallation ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_elements.DropdownMenu.Root, { children: [
1109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.DropdownMenu.Trigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.IconButton, { title: pipeActionsTitle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_icons.DotsHorizontalIcon, {}) }) }),
1110
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_elements.DropdownMenu.Content, { align: "end", children: [
1111
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1112
+ import_elements.DropdownMenu.Item,
1113
+ {
1114
+ onClick: () => setClientCredentialsOpen(true),
1115
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1116
+ import_translation.Translation,
1117
+ {
1118
+ defaultMessage: "Update client credentials",
1119
+ id: "daHKWE",
1120
+ description: "Menu option to update stored client credentials"
1121
+ }
1122
+ )
1123
+ }
1124
+ ),
1125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1126
+ import_elements.DropdownMenu.Item,
1127
+ {
1128
+ variant: "destructive",
1129
+ onClick: () => setDisconnectOpen(true),
1130
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1131
+ import_translation.Translation,
1132
+ {
1133
+ defaultMessage: "Disconnect account",
1134
+ id: "esZQrd",
1135
+ description: "Menu option to disconnect an account"
1136
+ }
1137
+ )
1138
+ }
1139
+ )
1140
+ ] })
1141
+ ] }) : connected && isApiKeyInstallation ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_elements.DropdownMenu.Root, { children: [
788
1142
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.DropdownMenu.Trigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.IconButton, { title: pipeActionsTitle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_icons.DotsHorizontalIcon, {}) }) }),
789
1143
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_elements.DropdownMenu.Content, { align: "end", children: [
790
1144
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_elements.DropdownMenu.Item, { onClick: () => setApiKeyOpen(true), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -896,34 +1250,9 @@ function ProviderStatus({ integration }) {
896
1250
  ] });
897
1251
  }
898
1252
  const authMethods = integration.authMethods ?? [];
899
- const supportsOAuth = authMethods.includes("oauth");
900
- const supportsApiKey = authMethods.includes("api_key");
901
- if (supportsOAuth && supportsApiKey) {
902
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { align: "center", gap: "4", children: [
903
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_status.Status, { state: "neutral", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
904
- import_translation.Translation,
905
- {
906
- defaultMessage: "Not connected",
907
- id: "J7bTZa",
908
- description: "Status text for an integration that is not connected"
909
- }
910
- ) }),
911
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DualMethodConnectButton, { integration })
912
- ] });
913
- }
914
- if (supportsApiKey) {
915
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { align: "center", gap: "4", children: [
916
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_status.Status, { state: "neutral", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
917
- import_translation.Translation,
918
- {
919
- defaultMessage: "Not connected",
920
- id: "J7bTZa",
921
- description: "Status text for an integration that is not connected"
922
- }
923
- ) }),
924
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApiKeyConnectButton, { integration })
925
- ] });
926
- }
1253
+ const methods = CONNECT_AUTH_METHODS.filter(
1254
+ (method) => authMethods.includes(method)
1255
+ );
927
1256
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_themes.Flex, { align: "center", gap: "4", children: [
928
1257
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_status.Status, { state: "neutral", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
929
1258
  import_translation.Translation,
@@ -933,7 +1262,7 @@ function ProviderStatus({ integration }) {
933
1262
  description: "Status text for an integration that is not connected"
934
1263
  }
935
1264
  ) }),
936
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectIntegrationButton, { integration })
1265
+ methods.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MultiMethodConnectButton, { integration, methods }) : methods.length === 1 && methods[0] === "api_key" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApiKeyConnectButton, { integration }) : methods.length === 1 && methods[0] === "client_credentials" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ClientCredentialsConnectButton, { integration }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectIntegrationButton, { integration })
937
1266
  ] });
938
1267
  }
939
1268
  function DisconnectAccountDialog({