@dev-fastn-ai/react-core 2.4.9 → 2.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +141 -100
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -24,7 +24,7 @@ Also, make sure you install the required **peer dependencies**:
24
24
  npm install react react-dom @tanstack/react-query
25
25
  ```
26
26
 
27
- > ✅ Requires React 18+
27
+ > ✅ Requires React 18 or 19
28
28
 
29
29
  ---
30
30
 
@@ -292,7 +292,15 @@ interface HideOptionConfig {
292
292
  readonly enable: boolean;
293
293
  readonly hideBasedOnValue: boolean;
294
294
  readonly key: string;
295
- readonly operation: "!=" | "==" | ">" | "<" | ">=" | "<=" | "contains" | "not_contains";
295
+ readonly operation:
296
+ | "!="
297
+ | "=="
298
+ | ">"
299
+ | "<"
300
+ | ">="
301
+ | "<="
302
+ | "contains"
303
+ | "not_contains";
296
304
  readonly value: string;
297
305
  }
298
306
  ```
@@ -315,14 +323,14 @@ const fieldConfig: ConnectorFieldConfig = {
315
323
  fileTypes: [],
316
324
  source: {
317
325
  flowId: "",
318
- isSameProject: false
326
+ isSameProject: false,
319
327
  },
320
328
  destination: {
321
329
  flowId: "",
322
- isSameProject: false
330
+ isSameProject: false,
323
331
  },
324
332
  isEditKeys: false,
325
- isAddFields: false
333
+ isAddFields: false,
326
334
  },
327
335
  disable: false,
328
336
  label: "Select Workspace",
@@ -331,8 +339,8 @@ const fieldConfig: ConnectorFieldConfig = {
331
339
  hideBasedOnValue: false,
332
340
  key: "",
333
341
  operation: "!=",
334
- value: ""
335
- }
342
+ value: "",
343
+ },
336
344
  };
337
345
 
338
346
  // Example connector field with configuration
@@ -344,27 +352,23 @@ const connectorField: ConnectorField = {
344
352
  required: true,
345
353
  placeholder: "Select a workspace",
346
354
  description: "Choose the workspace to connect to",
347
- configs: fieldConfig
355
+ configs: fieldConfig,
348
356
  };
349
357
 
350
358
  // Using the field in a React component
351
359
  function WorkspaceField({ field }: { field: ConnectorField }) {
352
360
  const { configs } = field;
353
-
361
+
354
362
  // Access configuration properties
355
363
  const isSelectionEnabled = configs?.selection?.enable;
356
364
  const flowId = configs?.selection?.flowId;
357
365
  const isDisabled = configs?.disable;
358
-
366
+
359
367
  return (
360
368
  <div className="field-container">
361
369
  <label>{field.label}</label>
362
- {isSelectionEnabled && (
363
- <p>Selection enabled for flow: {flowId}</p>
364
- )}
365
- <select disabled={isDisabled}>
366
- {/* Field options */}
367
- </select>
370
+ {isSelectionEnabled && <p>Selection enabled for flow: {flowId}</p>}
371
+ <select disabled={isDisabled}>{/* Field options */}</select>
368
372
  </div>
369
373
  );
370
374
  }
@@ -399,13 +403,13 @@ function ConnectorList() {
399
403
  <img src={connector.imageUri} alt={connector.name} />
400
404
  <h3>{connector.name}</h3>
401
405
  <p>{connector.description}</p>
402
-
406
+
403
407
  {connector.status === "ACTIVE" && (
404
408
  <span className="status-badge connected">Connected</span>
405
409
  )}
406
-
410
+
407
411
  {connector.actions?.map((action) => (
408
- <button
412
+ <button
409
413
  key={action.name}
410
414
  onClick={action.onClick}
411
415
  className={`action-btn ${action.actionType.toLowerCase()}`}
@@ -428,7 +432,11 @@ After a connector is activated, you can list its configurations:
428
432
  import { useConfigurations } from "@fastn-ai/react-core";
429
433
 
430
434
  function ConfigurationList({ configurationId }) {
431
- const { data: configurations, isLoading, error } = useConfigurations({ configurationId });
435
+ const {
436
+ data: configurations,
437
+ isLoading,
438
+ error,
439
+ } = useConfigurations({ configurationId });
432
440
  const [selectedConfig, setSelectedConfig] = useState(null);
433
441
 
434
442
  if (isLoading) return <div>Loading configurations...</div>;
@@ -446,21 +454,27 @@ function ConfigurationList({ configurationId }) {
446
454
  <p>{config.description}</p>
447
455
  </div>
448
456
  </div>
449
-
457
+
450
458
  <div className="config-actions">
451
459
  {config.status === "ENABLED" && (
452
460
  <span className="status-badge enabled">Active</span>
453
461
  )}
454
-
462
+
455
463
  {config.actions?.map((action) => (
456
464
  <button
457
465
  key={action.name}
458
466
  onClick={async () => {
459
467
  const result = await action.onClick();
460
- if (action.actionType === "ENABLE" && result?.status === "SUCCESS") {
468
+ if (
469
+ action.actionType === "ENABLE" &&
470
+ result?.status === "SUCCESS"
471
+ ) {
461
472
  // Show configuration form for new setup
462
473
  setSelectedConfig(config);
463
- } else if (action.actionType === "UPDATE" && result?.status === "SUCCESS") {
474
+ } else if (
475
+ action.actionType === "UPDATE" &&
476
+ result?.status === "SUCCESS"
477
+ ) {
464
478
  // Show configuration form for editing
465
479
  setSelectedConfig(config);
466
480
  }
@@ -473,9 +487,9 @@ function ConfigurationList({ configurationId }) {
473
487
  </div>
474
488
  </div>
475
489
  ))}
476
-
490
+
477
491
  {selectedConfig && (
478
- <ConfigurationForm
492
+ <ConfigurationForm
479
493
  configurationId={selectedConfig.id}
480
494
  onClose={() => setSelectedConfig(null)}
481
495
  />
@@ -522,7 +536,7 @@ function ConfigurationForm({ configurationId, onClose }) {
522
536
  const onSubmit = async (e) => {
523
537
  e.preventDefault();
524
538
  setIsSubmitting(true);
525
-
539
+
526
540
  try {
527
541
  await handleSubmit({ formData });
528
542
  console.log("Configuration saved successfully!");
@@ -539,9 +553,9 @@ function ConfigurationForm({ configurationId, onClose }) {
539
553
  <form onSubmit={onSubmit} className="configuration-form">
540
554
  <h2>Configure {configurationForm.name}</h2>
541
555
  <p>{configurationForm.description}</p>
542
-
556
+
543
557
  {configurationForm.fields.map((field) => (
544
- <FormField
558
+ <FormField
545
559
  key={field.key}
546
560
  field={field}
547
561
  value={formData[field.key]}
@@ -550,9 +564,11 @@ function ConfigurationForm({ configurationId, onClose }) {
550
564
  }
551
565
  />
552
566
  ))}
553
-
567
+
554
568
  <div className="form-actions">
555
- <button type="button" onClick={onClose}>Cancel</button>
569
+ <button type="button" onClick={onClose}>
570
+ Cancel
571
+ </button>
556
572
  <button type="submit" disabled={isSubmitting}>
557
573
  {isSubmitting ? "Saving..." : "Save Configuration"}
558
574
  </button>
@@ -678,27 +694,27 @@ The form fields handle different value types based on the field type:
678
694
  const formData = {
679
695
  // Select field - single object
680
696
  channel: { label: "General", value: "C123456" },
681
-
697
+
682
698
  // Multi-select field - array of objects
683
699
  channels: [
684
700
  { label: "General", value: "C123456" },
685
- { label: "Random", value: "C789012" }
701
+ { label: "Random", value: "C789012" },
686
702
  ],
687
-
703
+
688
704
  // Google Drive picker - single object
689
705
  folder: { label: "My Documents", value: "folder_id_123" },
690
-
706
+
691
707
  // Google Drive picker multi - array of objects
692
708
  files: [
693
709
  { label: "document1.pdf", value: "file_id_1" },
694
- { label: "document2.pdf", value: "file_id_2" }
710
+ { label: "document2.pdf", value: "file_id_2" },
695
711
  ],
696
-
712
+
697
713
  // Text field - primitive
698
714
  webhookUrl: "https://hooks.slack.com/...",
699
-
715
+
700
716
  // Boolean field - primitive
701
- enableNotifications: true
717
+ enableNotifications: true,
702
718
  };
703
719
  ```
704
720
 
@@ -713,7 +729,13 @@ For fields of type `select` or `multi-select`, use the `useFieldOptions` hook to
713
729
  ```tsx
714
730
  import { useFieldOptions } from "@fastn-ai/react-core";
715
731
 
716
- function SelectField({ field, value, onChange, isMulti = false, context = {} }) {
732
+ function SelectField({
733
+ field,
734
+ value,
735
+ onChange,
736
+ isMulti = false,
737
+ context = {},
738
+ }) {
717
739
  // context contains all form values and is used to fetch dependent options
718
740
  const {
719
741
  options,
@@ -737,17 +759,19 @@ function SelectField({ field, value, onChange, isMulti = false, context = {} })
737
759
  function handleSelectChange(selectedOptions) {
738
760
  if (isMulti) {
739
761
  // For multi-select, value should be an array of { label, value } objects
740
- const selectedValues = selectedOptions.map(option => ({
762
+ const selectedValues = selectedOptions.map((option) => ({
741
763
  label: option.label,
742
- value: option.value
764
+ value: option.value,
743
765
  }));
744
766
  onChange(selectedValues);
745
767
  } else {
746
768
  // For single select, value should be a single { label, value } object
747
- const selectedValue = selectedOptions[0] ? {
748
- label: selectedOptions[0].label,
749
- value: selectedOptions[0].value
750
- } : null;
769
+ const selectedValue = selectedOptions[0]
770
+ ? {
771
+ label: selectedOptions[0].label,
772
+ value: selectedOptions[0].value,
773
+ }
774
+ : null;
751
775
  onChange(selectedValue);
752
776
  }
753
777
  }
@@ -758,13 +782,13 @@ function SelectField({ field, value, onChange, isMulti = false, context = {} })
758
782
  {field.label}
759
783
  {field.required && <span className="required"> *</span>}
760
784
  </label>
761
-
785
+
762
786
  {error && (
763
787
  <div className="error-message">
764
788
  Error loading options: {error.message}
765
789
  </div>
766
790
  )}
767
-
791
+
768
792
  <input
769
793
  type="text"
770
794
  placeholder={field.placeholder || `Search ${field.label}`}
@@ -772,19 +796,23 @@ function SelectField({ field, value, onChange, isMulti = false, context = {} })
772
796
  disabled={loading}
773
797
  className="search-input"
774
798
  />
775
-
799
+
776
800
  <select
777
801
  multiple={isMulti}
778
- value={isMulti ? (value || []).map(v => v.value) : (value?.value || '')}
802
+ value={isMulti ? (value || []).map((v) => v.value) : value?.value || ""}
779
803
  onChange={(e) => {
780
804
  if (isMulti) {
781
- const selectedOptions = Array.from(e.target.selectedOptions).map(option => {
782
- const opt = options.find(o => o.value === option.value);
783
- return { label: opt.label, value: opt.value };
784
- });
805
+ const selectedOptions = Array.from(e.target.selectedOptions).map(
806
+ (option) => {
807
+ const opt = options.find((o) => o.value === option.value);
808
+ return { label: opt.label, value: opt.value };
809
+ }
810
+ );
785
811
  handleSelectChange(selectedOptions);
786
812
  } else {
787
- const selectedOption = options.find(o => o.value === e.target.value);
813
+ const selectedOption = options.find(
814
+ (o) => o.value === e.target.value
815
+ );
788
816
  handleSelectChange(selectedOption ? [selectedOption] : []);
789
817
  }
790
818
  }}
@@ -797,9 +825,9 @@ function SelectField({ field, value, onChange, isMulti = false, context = {} })
797
825
  </option>
798
826
  ))}
799
827
  </select>
800
-
828
+
801
829
  {loading && <div className="loading">Loading options...</div>}
802
-
830
+
803
831
  {hasNext && !loadingMore && (
804
832
  <button
805
833
  type="button"
@@ -809,13 +837,13 @@ function SelectField({ field, value, onChange, isMulti = false, context = {} })
809
837
  Load More
810
838
  </button>
811
839
  )}
812
-
840
+
813
841
  {loadingMore && <div className="loading">Loading more...</div>}
814
-
842
+
815
843
  <div className="options-info">
816
844
  Loaded {totalLoadedOptions} options{hasNext ? "" : " (all loaded)"}
817
845
  </div>
818
-
846
+
819
847
  {field.description && (
820
848
  <div className="field-description">{field.description}</div>
821
849
  )}
@@ -829,23 +857,29 @@ function SelectField({ field, value, onChange, isMulti = false, context = {} })
829
857
  For Google Drive file picker fields, handle the file selection flow. These fields also work with `{ label, value }` objects and support file type filtering:
830
858
 
831
859
  ```tsx
832
- function GoogleFilesPickerField({ field, value, onChange, isMulti = false, context = {} }) {
860
+ function GoogleFilesPickerField({
861
+ field,
862
+ value,
863
+ onChange,
864
+ isMulti = false,
865
+ context = {},
866
+ }) {
833
867
  async function handlePickFiles() {
834
868
  if (field.optionsSource?.openGoogleFilesPicker) {
835
869
  await field.optionsSource.openGoogleFilesPicker({
836
870
  onComplete: async (files) => {
837
871
  if (isMulti) {
838
872
  // For multi-select, ensure we have an array of { label, value } objects
839
- const formattedFiles = files.map(file => ({
873
+ const formattedFiles = files.map((file) => ({
840
874
  label: file.label || file.name || file.value,
841
- value: file.value || file.id
875
+ value: file.value || file.id,
842
876
  }));
843
877
  onChange(formattedFiles);
844
878
  } else {
845
879
  // For single select, ensure we have a single { label, value } object
846
880
  const formattedFile = {
847
881
  label: files[0]?.label || files[0]?.name || files[0]?.value,
848
- value: files[0]?.value || files[0]?.id
882
+ value: files[0]?.value || files[0]?.id,
849
883
  };
850
884
  onChange(formattedFile);
851
885
  }
@@ -865,15 +899,15 @@ function GoogleFilesPickerField({ field, value, onChange, isMulti = false, conte
865
899
  {field.label}
866
900
  {field.required && <span className="required"> *</span>}
867
901
  </label>
868
-
869
- <button
870
- type="button"
902
+
903
+ <button
904
+ type="button"
871
905
  onClick={handlePickFiles}
872
906
  className="google-picker-btn"
873
907
  >
874
908
  Pick from Google Drive
875
909
  </button>
876
-
910
+
877
911
  {value && (
878
912
  <div className="selected-files">
879
913
  <strong>Selected file{isMulti ? "s" : ""}:</strong>
@@ -884,7 +918,7 @@ function GoogleFilesPickerField({ field, value, onChange, isMulti = false, conte
884
918
  </ul>
885
919
  </div>
886
920
  )}
887
-
921
+
888
922
  {field.description && (
889
923
  <div className="field-description">{field.description}</div>
890
924
  )}
@@ -923,7 +957,7 @@ function FormField({ field, value, onChange, context = {} }) {
923
957
  )}
924
958
  </div>
925
959
  );
926
-
960
+
927
961
  case "checkbox":
928
962
  return (
929
963
  <div className="field-container">
@@ -943,51 +977,51 @@ function FormField({ field, value, onChange, context = {} }) {
943
977
  )}
944
978
  </div>
945
979
  );
946
-
980
+
947
981
  case "select":
948
982
  return (
949
- <SelectField
950
- field={field}
951
- value={value}
952
- onChange={onChange}
983
+ <SelectField
984
+ field={field}
985
+ value={value}
986
+ onChange={onChange}
953
987
  isMulti={false}
954
988
  context={context}
955
989
  />
956
990
  );
957
-
991
+
958
992
  case "multi-select":
959
993
  return (
960
- <SelectField
961
- field={field}
962
- value={value}
963
- onChange={onChange}
994
+ <SelectField
995
+ field={field}
996
+ value={value}
997
+ onChange={onChange}
964
998
  isMulti={true}
965
999
  context={context}
966
1000
  />
967
1001
  );
968
-
1002
+
969
1003
  case "google-files-picker-select":
970
1004
  return (
971
- <GoogleFilesPickerField
972
- field={field}
973
- value={value}
974
- onChange={onChange}
1005
+ <GoogleFilesPickerField
1006
+ field={field}
1007
+ value={value}
1008
+ onChange={onChange}
975
1009
  isMulti={false}
976
1010
  context={context}
977
1011
  />
978
1012
  );
979
-
1013
+
980
1014
  case "google-files-picker-multi-select":
981
1015
  return (
982
- <GoogleFilesPickerField
983
- field={field}
984
- value={value}
985
- onChange={onChange}
1016
+ <GoogleFilesPickerField
1017
+ field={field}
1018
+ value={value}
1019
+ onChange={onChange}
986
1020
  isMulti={true}
987
1021
  context={context}
988
1022
  />
989
1023
  );
990
-
1024
+
991
1025
  default:
992
1026
  return (
993
1027
  <div className="field-container">
@@ -1022,17 +1056,18 @@ Here's how to implement context passing in your form components:
1022
1056
  import { useConfigurationForm } from "@fastn-ai/react-core";
1023
1057
 
1024
1058
  function ConfigurationForm({ configurationId, connectorId, configuration }) {
1025
- const { data: configurationForm, isLoading, error } = useConfigurationForm({
1059
+ const {
1060
+ data: configurationForm,
1061
+ isLoading,
1062
+ error,
1063
+ } = useConfigurationForm({
1026
1064
  configurationId,
1027
1065
  connectorId,
1028
1066
  configuration,
1029
1067
  });
1030
1068
 
1031
1069
  return (
1032
- <Formik
1033
- initialValues={initialValues}
1034
- onSubmit={handleSubmit}
1035
- >
1070
+ <Formik initialValues={initialValues} onSubmit={handleSubmit}>
1036
1071
  {({ values, setFieldValue, setFieldTouched, errors, touched }) => (
1037
1072
  <Form>
1038
1073
  {configurationForm.fields
@@ -1156,7 +1191,9 @@ function MultiLevelSelectForm() {
1156
1191
  <SelectField
1157
1192
  field={workspaceField}
1158
1193
  value={formValues.workspace}
1159
- onChange={(value) => setFormValues(prev => ({ ...prev, workspace: value }))}
1194
+ onChange={(value) =>
1195
+ setFormValues((prev) => ({ ...prev, workspace: value }))
1196
+ }
1160
1197
  context={formValues}
1161
1198
  />
1162
1199
 
@@ -1164,7 +1201,9 @@ function MultiLevelSelectForm() {
1164
1201
  <SelectField
1165
1202
  field={channelField}
1166
1203
  value={formValues.channel}
1167
- onChange={(value) => setFormValues(prev => ({ ...prev, channel: value }))}
1204
+ onChange={(value) =>
1205
+ setFormValues((prev) => ({ ...prev, channel: value }))
1206
+ }
1168
1207
  context={formValues} // Has access to workspace value
1169
1208
  />
1170
1209
 
@@ -1172,7 +1211,9 @@ function MultiLevelSelectForm() {
1172
1211
  <SelectField
1173
1212
  field={userField}
1174
1213
  value={formValues.user}
1175
- onChange={(value) => setFormValues(prev => ({ ...prev, user: value }))}
1214
+ onChange={(value) =>
1215
+ setFormValues((prev) => ({ ...prev, user: value }))
1216
+ }
1176
1217
  context={formValues} // Has access to both workspace and channel values
1177
1218
  />
1178
1219
  </div>
@@ -1225,7 +1266,6 @@ function ConnectorManager() {
1225
1266
  }
1226
1267
  ```
1227
1268
 
1228
-
1229
1269
  ```tsx
1230
1270
  function ConfigurationActions({ config }) {
1231
1271
  const queryClient = useQueryClient();
@@ -1273,6 +1313,7 @@ function ConfigurationActions({ config }) {
1273
1313
  ```
1274
1314
 
1275
1315
  ---
1316
+
1276
1317
  ## 🚨 Troubleshooting
1277
1318
 
1278
1319
  ### **Common Issues**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev-fastn-ai/react-core",
3
- "version": "2.4.9",
3
+ "version": "2.4.12",
4
4
  "description": "React hooks and components for integrating Fastn AI connector marketplace into your applications. Built on top of @fastn-ai/core with React Query for optimal performance.",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -66,15 +66,15 @@
66
66
  },
67
67
  "peerDependencies": {
68
68
  "@tanstack/react-query": "^5.0.0",
69
- "react": "^18.0.0",
70
- "react-dom": "^18.0.0"
69
+ "react": "^18.0.0 || ^19.0.0",
70
+ "react-dom": "^18.0.0 || ^19.0.0"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@rollup/plugin-commonjs": "^28.0.1",
74
74
  "@rollup/plugin-json": "^6.1.0",
75
75
  "@rollup/plugin-node-resolve": "^15.3.0",
76
76
  "@types/node": "^20.11.24",
77
- "@types/react": "^18.0.29",
77
+ "@types/react": "^18.0.29 || ^19.0.0",
78
78
  "@types/react-dom": "^18.0.11",
79
79
  "rimraf": "^5.0.5",
80
80
  "rollup": "^4.12.0",