@dev-fastn-ai/react-core 1.0.16 → 1.0.18

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 CHANGED
@@ -1,1265 +1,18 @@
1
- <!-- # @fastn-ai/react-core
2
-
3
- A powerful React library for seamlessly integrating the Fastn AI connector marketplace into your applications. This package provides a complete set of hooks and components to manage connectors, configurations, and dynamic forms with full TypeScript support.
4
-
5
- ## 🚀 Features
6
-
7
- - **🔌 Connector Management**: Fetch, display, and manage marketplace connectors
8
- - **⚙️ Configuration Handling**: Create, update, and manage connector configurations
9
- - **📝 Dynamic Forms**: Render configuration forms with async field options
10
- - **🔄 Real-time Updates**: Built-in caching and background refetching
11
- - **🎨 Customizable**: Full styling control and component customization
12
- - **📱 TypeScript First**: Complete type safety and IntelliSense support
13
- - **⚡ Performance Optimized**: React Query powered with intelligent caching
14
-
15
- ## 📦 Installation
16
-
17
- ```bash
18
- npm install @fastn-ai/react-core
19
- ```
20
-
21
- ### Peer Dependencies
22
-
23
- This package requires React 18+ and React Query. Make sure you have these installed:
24
-
25
- ```bash
26
- npm install react react-dom @tanstack/react-query
27
- ```
28
-
29
- ## 🏗️ Quick Start
30
-
31
- ### 1. Setup the Provider
32
-
33
- First, wrap your application with the `FastnProvider` and provide your configuration:
34
-
35
- ```tsx
36
- import { FastnProvider } from '@fastn-ai/react-core';
37
-
38
- function App() {
39
- const fastnConfig = {
40
- baseUrl: 'https://api.fastn.ai', // Optional: defaults to production
41
- environment: 'LIVE', // 'LIVE' | 'DRAFT' | custom string
42
- authToken: 'your-auth-token',
43
- tenantId: 'your-tenant-id',
44
- spaceId: 'your-space-id',
45
- customAuth: false, // Optional: for custom authentication
46
- };
47
-
48
- return (
49
- <FastnProvider config={fastnConfig}>
50
- <YourApp />
51
- </FastnProvider>
52
- );
53
- }
54
- ```
55
-
56
- ### 2. Use the Hooks
57
-
58
- Now you can use any of the provided hooks in your components:
59
-
60
- ```tsx
61
- import { useConnectors, useConfigurations } from '@fastn-ai/react-core';
62
-
63
- function MyComponent() {
64
- const { data: connectors, isLoading, error } = useConnectors();
65
-
66
- if (isLoading) return <div>Loading connectors...</div>;
67
- if (error) return <div>Error: {error.message}</div>;
68
-
69
- return (
70
- <div>
71
- {connectors?.map(connector => (
72
- <ConnectorCard key={connector.id} connector={connector} />
73
- ))}
74
- </div>
75
- );
76
- }
77
- ```
78
-
79
- ## 📚 Detailed API Reference
80
-
81
- ### FastnProvider
82
-
83
- The main provider component that initializes the Fastn client and provides React Query context.
84
-
85
- #### Props
86
-
87
- ```tsx
88
- interface FastnProviderProps {
89
- children: React.ReactNode;
90
- config: Required<FastnConfig>;
91
- }
92
-
93
- interface FastnConfig {
94
- baseUrl?: string;
95
- environment?: FastnEnvironment;
96
- authToken: string;
97
- tenantId: string;
98
- spaceId: string;
99
- customAuth?: boolean;
100
- }
101
-
102
- type FastnEnvironment = 'LIVE' | 'DRAFT' | string;
103
- ```
104
-
105
- #### Example
106
-
107
- ```tsx
108
- import { FastnProvider } from '@fastn-ai/react-core';
109
-
110
- function App() {
111
- const config = {
112
- baseUrl: 'https://api.fastn.ai',
113
- environment: 'LIVE',
114
- authToken: process.env.REACT_APP_FASTN_AUTH_TOKEN!,
115
- tenantId: process.env.REACT_APP_FASTN_TENANT_ID!,
116
- spaceId: process.env.REACT_APP_FASTN_SPACE_ID!,
117
- customAuth: false,
118
- };
119
-
120
- return (
121
- <FastnProvider config={config}>
122
- <Router>
123
- <Routes>
124
- <Route path="/connectors" element={<ConnectorsPage />} />
125
- <Route path="/configurations" element={<ConfigurationsPage />} />
126
- </Routes>
127
- </Router>
128
- </FastnProvider>
129
- );
130
- }
131
- ```
132
-
133
- ### useConnectors
134
-
135
- Fetches and manages the list of available connectors from the marketplace.
136
-
137
- #### Usage
138
-
139
- ```tsx
140
- import { useConnectors } from '@fastn-ai/react-core';
141
-
142
- function ConnectorsList() {
143
- const {
144
- data: connectors,
145
- isLoading,
146
- error,
147
- refetch
148
- } = useConnectors();
149
-
150
- if (isLoading) return <ConnectorsSkeleton />;
151
- if (error) return <ErrorMessage error={error} />;
152
-
153
- return (
154
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
155
- {connectors?.map(connector => (
156
- <ConnectorCard
157
- key={connector.id}
158
- connector={connector}
159
- onRefresh={refetch}
160
- />
161
- ))}
162
- </div>
163
- );
164
- }
165
- ```
166
-
167
- #### Return Value
168
-
169
- ```tsx
170
- interface UseConnectorsReturn {
171
- data: Connector[] | undefined;
172
- isLoading: boolean;
173
- isError: boolean;
174
- error: Error | null;
175
- refetch: () => Promise<any>;
176
- isFetching: boolean;
177
- isSuccess: boolean;
178
- }
179
- ```
180
-
181
- #### Connector Type
182
-
183
- ```tsx
184
- interface Connector {
185
- readonly id: string;
186
- readonly name: string;
187
- readonly description: string;
188
- readonly imageUri?: string;
189
- readonly status: ConnectorStatus;
190
- readonly actions: readonly ConnectorAction[];
191
- }
192
-
193
- enum ConnectorStatus {
194
- ACTIVE = "ACTIVE",
195
- INACTIVE = "INACTIVE",
196
- ALL = "ALL",
197
- }
198
-
199
- interface ConnectorAction {
200
- readonly name: string;
201
- readonly actionType: ConnectorActionType | string;
202
- readonly form?: ConnectorForm | null;
203
- readonly onClick?: (data?: unknown) => Promise<unknown>;
204
- readonly onSubmit?: (formData: Record<string, unknown>) => Promise<unknown>;
205
- }
206
-
207
- enum ConnectorActionType {
208
- ACTIVATION = "ACTIVATION",
209
- DEACTIVATION = "DEACTIVATION",
210
- NONE = "NONE",
211
- ENABLE = "ENABLE",
212
- DISABLE = "DISABLE",
213
- DELETE = "DELETE",
214
- }
215
- ```
216
-
217
- ### useConfigurations
218
-
219
- Fetches and manages connector configurations for a specific configuration ID.
220
-
221
- #### Usage
222
-
223
- ```tsx
224
- import { useConfigurations } from '@fastn-ai/react-core';
225
-
226
- function ConfigurationsList({ configurationId }: { configurationId: string }) {
227
- const {
228
- data: configurations,
229
- isLoading,
230
- error
231
- } = useConfigurations({
232
- configurationId,
233
- status: 'ALL' // 'ENABLED' | 'DISABLED' | 'ALL' | 'IDLE'
234
- });
235
-
236
- if (isLoading) return <ConfigurationsSkeleton />;
237
- if (error) return <ErrorMessage error={error} />;
238
-
239
- return (
240
- <div className="space-y-4">
241
- {configurations?.map(configuration => (
242
- <ConfigurationCard
243
- key={configuration.id}
244
- configuration={configuration}
245
- />
246
- ))}
247
- </div>
248
- );
249
- }
250
- ```
251
-
252
- #### Parameters
253
-
254
- ```tsx
255
- interface GetConfigurationsInput {
256
- readonly configurationId: string;
257
- readonly status?: "ENABLED" | "DISABLED" | "ALL" | "IDLE";
258
- }
259
- ```
260
-
261
- #### Return Value
262
-
263
- ```tsx
264
- interface UseConfigurationsReturn {
265
- data: Configuration[] | undefined;
266
- isLoading: boolean;
267
- isError: boolean;
268
- error: Error | null;
269
- refetch: () => Promise<any>;
270
- isFetching: boolean;
271
- isSuccess: boolean;
272
- }
273
- ```
274
-
275
- #### Configuration Type
276
-
277
- ```tsx
278
- interface Configuration {
279
- readonly id: string;
280
- readonly connectorId: string;
281
- readonly configurationId: string;
282
- readonly name: string;
283
- readonly flowId: string;
284
- readonly description: string;
285
- readonly imageUri: string;
286
- readonly status: string;
287
- readonly actions: readonly ConfigurationAction[];
288
- readonly metadata?: unknown;
289
- }
290
-
291
- interface ConfigurationAction {
292
- readonly name: string;
293
- readonly actionType: ConfigurationActionType;
294
- readonly onClick?: () => Promise<void>;
295
- readonly form?: ConnectorForm | null;
296
- readonly onSubmit?: (formData: unknown) => Promise<void>;
297
- }
298
-
299
- type ConfigurationActionType = ConnectorActionType.ENABLE | ConnectorActionType.DISABLE | ConnectorActionType.DELETE;
300
- ```
301
-
302
- ### useConfigurationForm
303
-
304
- Fetches and manages configuration forms for setting up connectors.
305
-
306
- #### Usage
307
-
308
- ```tsx
309
- import { useConfigurationForm } from '@fastn-ai/react-core';
310
-
311
- function ConfigurationForm({
312
- configurationId,
313
- connectorId,
314
- configuration
315
- }: {
316
- configurationId: string;
317
- connectorId: string;
318
- configuration: Configuration;
319
- }) {
320
- const {
321
- data: form,
322
- isLoading,
323
- error
324
- } = useConfigurationForm({
325
- configurationId,
326
- connectorId,
327
- configuration
328
- });
329
-
330
- if (isLoading) return <FormSkeleton />;
331
- if (error) return <ErrorMessage error={error} />;
332
- if (!form) return <div>No form available</div>;
333
-
334
- return (
335
- <ConfigurationFormRenderer
336
- form={form}
337
- onSubmit={handleSubmit}
338
- onCancel={handleCancel}
339
- />
340
- );
341
- }
342
- ```
343
-
344
- #### Parameters
345
-
346
- ```tsx
347
- interface GetConfigurationFormInput {
348
- readonly configurationId: string;
349
- readonly connectorId: string;
350
- readonly configuration: Configuration;
351
- }
352
- ```
353
-
354
- #### Return Value
355
-
356
- ```tsx
357
- interface UseConfigurationFormReturn {
358
- data: ConfigurationForm | undefined;
359
- isLoading: boolean;
360
- isError: boolean;
361
- error: Error | null;
362
- refetch: () => Promise<any>;
363
- isFetching: boolean;
364
- isSuccess: boolean;
365
- }
366
- ```
367
-
368
- #### ConfigurationForm Type
369
-
370
- ```tsx
371
- interface ConfigurationForm {
372
- readonly name: string;
373
- readonly description: string;
374
- readonly imageUri: string;
375
- readonly fields: readonly ConnectorField[];
376
- readonly submitButtonLabel?: string;
377
- readonly loading?: boolean;
378
- readonly error?: string;
379
- readonly submitHandler?: (args: { formData: FormData }) => Promise<void>;
380
- }
381
- ```
382
-
383
- ### useFieldOptions
384
-
385
- Manages async select field options with search, pagination, and caching.
386
-
387
- #### Usage
388
-
389
- ```tsx
390
- import { useFieldOptions } from '@fastn-ai/react-core';
391
-
392
- function AsyncSelectField({ field }: { field: ConnectorField }) {
393
- const {
394
- options,
395
- loading,
396
- loadingMore,
397
- hasNext,
398
- query,
399
- refresh,
400
- search,
401
- loadMore,
402
- error
403
- } = useFieldOptions(field);
404
-
405
- return (
406
- <div>
407
- <AsyncSelect
408
- options={options}
409
- isLoading={loading}
410
- onInputChange={search}
411
- onMenuScrollToBottom={loadMore}
412
- loadMore={loadingMore}
413
- hasMore={hasNext}
414
- onRefresh={refresh}
415
- placeholder={field.placeholder}
416
- isClearable
417
- isSearchable
418
- />
419
- {error && <ErrorMessage error={error} />}
420
- </div>
421
- );
422
- }
423
- ```
424
-
425
- #### Return Value
426
-
427
- ```tsx
428
- interface UseFieldOptionsReturn {
429
- options: SelectOption[];
430
- loading: boolean;
431
- loadingMore: boolean;
432
- hasNext: boolean;
433
- query: string;
434
- refresh: () => Promise<void>;
435
- search: (query: string) => void;
436
- loadMore: () => Promise<void>;
437
- totalLoadedOptions: number;
438
- error: string | null;
439
- }
440
- ```
441
-
442
- #### SelectOption Type
443
-
444
- ```tsx
445
- interface SelectOption {
446
- readonly label: string;
447
- readonly value: string;
448
- }
449
- ```
450
-
451
- ## 🎨 Component Examples
452
-
453
- ### Connector Card Component
454
-
455
- ```tsx
456
- import { useConnectors } from '@fastn-ai/react-core';
457
- import type { Connector } from '@fastn-ai/react-core';
458
-
459
- interface ConnectorCardProps {
460
- connector: Connector;
461
- onRefresh?: () => void;
462
- }
463
-
464
- function ConnectorCard({ connector, onRefresh }: ConnectorCardProps) {
465
- const handleAction = async (action: ConnectorAction) => {
466
- try {
467
- if (action.onClick) {
468
- await action.onClick();
469
- onRefresh?.();
470
- }
471
- } catch (error) {
472
- console.error('Action failed:', error);
473
- }
474
- };
475
-
476
- return (
477
- <div className="border rounded-lg p-6 shadow-sm hover:shadow-md transition-shadow">
478
- <div className="flex items-center space-x-4">
479
- {connector.imageUri && (
480
- <img
481
- src={connector.imageUri}
482
- alt={connector.name}
483
- className="w-12 h-12 rounded-lg object-cover"
484
- />
485
- )}
486
- <div className="flex-1">
487
- <h3 className="text-lg font-semibold text-gray-900">
488
- {connector.name}
489
- </h3>
490
- <p className="text-sm text-gray-600">
491
- {connector.description}
492
- </p>
493
- <span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
494
- connector.status === 'ACTIVE'
495
- ? 'bg-green-100 text-green-800'
496
- : 'bg-gray-100 text-gray-800'
497
- }`}>
498
- {connector.status}
499
- </span>
500
- </div>
501
- </div>
502
-
503
- <div className="mt-4 flex space-x-2">
504
- {connector.actions.map((action, index) => (
505
- <button
506
- key={index}
507
- onClick={() => handleAction(action)}
508
- className={`px-4 py-2 text-sm font-medium rounded-md transition-colors ${
509
- action.actionType === 'ACTIVATION' || action.actionType === 'ENABLE'
510
- ? 'bg-blue-600 text-white hover:bg-blue-700'
511
- : action.actionType === 'DEACTIVATION' || action.actionType === 'DISABLE'
512
- ? 'bg-red-600 text-white hover:bg-red-700'
513
- : 'bg-gray-600 text-white hover:bg-gray-700'
514
- }`}
515
- >
516
- {action.name}
517
- </button>
518
- ))}
519
- </div>
520
- </div>
521
- );
522
- }
523
- ```
524
-
525
- ### Configuration Card Component
526
-
527
- ```tsx
528
- import { useConfigurations } from '@fastn-ai/react-core';
529
- import type { Configuration } from '@fastn-ai/react-core';
530
-
531
- interface ConfigurationCardProps {
532
- configuration: Configuration;
533
- }
534
-
535
- function ConfigurationCard({ configuration }: ConfigurationCardProps) {
536
- const handleAction = async (action: ConfigurationAction) => {
537
- try {
538
- if (action.onClick) {
539
- await action.onClick();
540
- }
541
- } catch (error) {
542
- console.error('Configuration action failed:', error);
543
- }
544
- };
545
-
546
- return (
547
- <div className="border rounded-lg p-6 shadow-sm">
548
- <div className="flex items-center space-x-4">
549
- <img
550
- src={configuration.imageUri}
551
- alt={configuration.name}
552
- className="w-12 h-12 rounded-lg object-cover"
553
- />
554
- <div className="flex-1">
555
- <h3 className="text-lg font-semibold text-gray-900">
556
- {configuration.name}
557
- </h3>
558
- <p className="text-sm text-gray-600">
559
- {configuration.description}
560
- </p>
561
- <div className="flex items-center space-x-2 mt-2">
562
- <span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
563
- configuration.status === 'ENABLED'
564
- ? 'bg-green-100 text-green-800'
565
- : configuration.status === 'DISABLED'
566
- ? 'bg-red-100 text-red-800'
567
- : 'bg-yellow-100 text-yellow-800'
568
- }`}>
569
- {configuration.status}
570
- </span>
571
- <span className="text-xs text-gray-500">
572
- ID: {configuration.id}
573
- </span>
574
- </div>
575
- </div>
576
- </div>
577
-
578
- <div className="mt-4 flex space-x-2">
579
- {configuration.actions.map((action, index) => (
580
- <button
581
- key={index}
582
- onClick={() => handleAction(action)}
583
- className={`px-4 py-2 text-sm font-medium rounded-md transition-colors ${
584
- action.actionType === 'ENABLE'
585
- ? 'bg-green-600 text-white hover:bg-green-700'
586
- : action.actionType === 'DISABLE'
587
- ? 'bg-yellow-600 text-white hover:bg-yellow-700'
588
- : action.actionType === 'DELETE'
589
- ? 'bg-red-600 text-white hover:bg-red-700'
590
- : 'bg-gray-600 text-white hover:bg-gray-700'
591
- }`}
592
- >
593
- {action.name}
594
- </button>
595
- ))}
596
- </div>
597
- </div>
598
- );
599
- }
600
- ```
601
-
602
- ### Configuration Form Renderer
603
-
604
- ```tsx
605
- import { useConfigurationForm, useFieldOptions } from '@fastn-ai/react-core';
606
- import type { ConfigurationForm, ConnectorField } from '@fastn-ai/react-core';
607
-
608
- interface ConfigurationFormRendererProps {
609
- form: ConfigurationForm;
610
- onSubmit: (formData: Record<string, any>) => Promise<void>;
611
- onCancel: () => void;
612
- }
613
-
614
- function ConfigurationFormRenderer({
615
- form,
616
- onSubmit,
617
- onCancel
618
- }: ConfigurationFormRendererProps) {
619
- const [formData, setFormData] = useState<Record<string, any>>({});
620
- const [isSubmitting, setIsSubmitting] = useState(false);
621
-
622
- const handleSubmit = async (e: React.FormEvent) => {
623
- e.preventDefault();
624
- setIsSubmitting(true);
625
-
626
- try {
627
- await onSubmit(formData);
628
- } catch (error) {
629
- console.error('Form submission failed:', error);
630
- } finally {
631
- setIsSubmitting(false);
632
- }
633
- };
634
-
635
- const renderField = (field: ConnectorField) => {
636
- switch (field.type) {
637
- case 'text':
638
- case 'email':
639
- case 'password':
640
- return (
641
- <input
642
- type={field.type}
643
- name={field.key}
644
- placeholder={field.placeholder}
645
- required={field.required}
646
- disabled={field.disabled}
647
- value={formData[field.key] || ''}
648
- onChange={(e) => setFormData(prev => ({
649
- ...prev,
650
- [field.key]: e.target.value
651
- }))}
652
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
653
- />
654
- );
655
-
656
- case 'select':
657
- return <AsyncSelectField field={field} />;
658
-
659
- case 'multi-select':
660
- return <AsyncMultiSelectField field={field} />;
661
-
662
- case 'number':
663
- return (
664
- <input
665
- type="number"
666
- name={field.key}
667
- placeholder={field.placeholder}
668
- required={field.required}
669
- disabled={field.disabled}
670
- value={formData[field.key] || ''}
671
- onChange={(e) => setFormData(prev => ({
672
- ...prev,
673
- [field.key]: e.target.value
674
- }))}
675
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
676
- />
677
- );
678
-
679
- case 'checkbox':
680
- return (
681
- <input
682
- type="checkbox"
683
- name={field.key}
684
- required={field.required}
685
- disabled={field.disabled}
686
- checked={formData[field.key] || false}
687
- onChange={(e) => setFormData(prev => ({
688
- ...prev,
689
- [field.key]: e.target.checked
690
- }))}
691
- className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
692
- />
693
- );
694
-
695
- default:
696
- return (
697
- <input
698
- type="text"
699
- name={field.key}
700
- placeholder={field.placeholder}
701
- required={field.required}
702
- disabled={field.disabled}
703
- value={formData[field.key] || ''}
704
- onChange={(e) => setFormData(prev => ({
705
- ...prev,
706
- [field.key]: e.target.value
707
- }))}
708
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
709
- />
710
- );
711
- }
712
- };
713
-
714
- return (
715
- <div className="max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-lg">
716
- <div className="mb-6">
717
- <h2 className="text-2xl font-bold text-gray-900">{form.name}</h2>
718
- <p className="text-gray-600 mt-2">{form.description}</p>
719
- </div>
720
-
721
- <form onSubmit={handleSubmit} className="space-y-6">
722
- {form.fields.map((field) => (
723
- <div key={field.key} className="space-y-2">
724
- <label className="block text-sm font-medium text-gray-700">
725
- {field.label}
726
- {field.required && <span className="text-red-500 ml-1">*</span>}
727
- </label>
728
- {renderField(field)}
729
- {field.description && (
730
- <p className="text-sm text-gray-500">{field.description}</p>
731
- )}
732
- </div>
733
- ))}
734
-
735
- <div className="flex space-x-4 pt-6">
736
- <button
737
- type="submit"
738
- disabled={isSubmitting}
739
- className="flex-1 bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
740
- >
741
- {isSubmitting ? 'Saving...' : (form.submitButtonLabel || 'Save Configuration')}
742
- </button>
743
- <button
744
- type="button"
745
- onClick={onCancel}
746
- className="flex-1 bg-gray-300 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-400"
747
- >
748
- Cancel
749
- </button>
750
- </div>
751
- </form>
752
- </div>
753
- );
754
- }
755
- ```
756
-
757
- ### Async Select Field Component
758
-
759
- ```tsx
760
- import { useFieldOptions } from '@fastn-ai/react-core';
761
- import type { ConnectorField, SelectOption } from '@fastn-ai/react-core';
762
-
763
- interface AsyncSelectFieldProps {
764
- field: ConnectorField;
765
- value?: SelectOption | null;
766
- onChange?: (option: SelectOption | null) => void;
767
- }
768
-
769
- function AsyncSelectField({ field, value, onChange }: AsyncSelectFieldProps) {
770
- const {
771
- options,
772
- loading,
773
- loadingMore,
774
- hasNext,
775
- query,
776
- refresh,
777
- search,
778
- loadMore,
779
- error
780
- } = useFieldOptions(field);
781
-
782
- const handleInputChange = (inputValue: string) => {
783
- search(inputValue);
784
- };
785
-
786
- const handleMenuScrollToBottom = () => {
787
- if (hasNext && !loadingMore) {
788
- loadMore();
789
- }
790
- };
791
-
792
- return (
793
- <div className="relative">
794
- <select
795
- value={value?.value || ''}
796
- onChange={(e) => {
797
- const option = options.find(opt => opt.value === e.target.value);
798
- onChange?.(option || null);
799
- }}
800
- disabled={loading}
801
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
802
- >
803
- <option value="">{field.placeholder || 'Select an option...'}</option>
804
- {options.map((option) => (
805
- <option key={option.value} value={option.value}>
806
- {option.label}
807
- </option>
808
- ))}
809
- </select>
810
-
811
- {loading && (
812
- <div className="absolute right-3 top-1/2 transform -translate-y-1/2">
813
- <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600"></div>
814
- </div>
815
- )}
816
-
817
- {hasNext && (
818
- <button
819
- type="button"
820
- onClick={loadMore}
821
- disabled={loadingMore}
822
- className="mt-2 text-sm text-blue-600 hover:text-blue-800 disabled:opacity-50"
823
- >
824
- {loadingMore ? 'Loading more...' : 'Load more options'}
825
- </button>
826
- )}
827
-
828
- {error && (
829
- <p className="mt-1 text-sm text-red-600">{error}</p>
830
- )}
831
- </div>
832
- );
833
- }
834
- ```
835
-
836
- ## 🔧 Advanced Usage
837
-
838
- ### Custom Error Handling
839
-
840
- ```tsx
841
- import { useConnectors } from '@fastn-ai/react-core';
842
-
843
- function ConnectorsWithErrorHandling() {
844
- const { data: connectors, isLoading, error, refetch } = useConnectors();
845
-
846
- if (error) {
847
- return (
848
- <div className="bg-red-50 border border-red-200 rounded-md p-4">
849
- <div className="flex">
850
- <div className="flex-shrink-0">
851
- <svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
852
- <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
853
- </svg>
854
- </div>
855
- <div className="ml-3">
856
- <h3 className="text-sm font-medium text-red-800">
857
- Failed to load connectors
858
- </h3>
859
- <div className="mt-2 text-sm text-red-700">
860
- <p>{error.message}</p>
861
- </div>
862
- <div className="mt-4">
863
- <button
864
- onClick={() => refetch()}
865
- className="bg-red-100 text-red-800 px-3 py-2 rounded-md text-sm font-medium hover:bg-red-200"
866
- >
867
- Try again
868
- </button>
869
- </div>
870
- </div>
871
- </div>
872
- </div>
873
- );
874
- }
875
-
876
- // ... rest of component
877
- }
878
- ```
879
-
880
- ### Optimistic Updates
881
-
882
- ```tsx
883
- import { useConnectors } from '@fastn-ai/react-core';
884
- import { useQueryClient } from '@tanstack/react-query';
885
-
886
- function ConnectorWithOptimisticUpdate({ connector }: { connector: Connector }) {
887
- const queryClient = useQueryClient();
888
-
889
- const handleAction = async (action: ConnectorAction) => {
890
- if (!action.onClick) return;
891
-
892
- // Optimistically update the UI
893
- queryClient.setQueryData(['connectors'], (oldData: Connector[] | undefined) => {
894
- if (!oldData) return oldData;
895
-
896
- return oldData.map(conn => {
897
- if (conn.id === connector.id) {
898
- return {
899
- ...conn,
900
- status: action.actionType === 'ACTIVATION' ? 'ACTIVE' : 'INACTIVE'
901
- };
902
- }
903
- return conn;
904
- });
905
- });
906
-
907
- try {
908
- await action.onClick();
909
- // Refetch to ensure data consistency
910
- queryClient.invalidateQueries(['connectors']);
911
- } catch (error) {
912
- // Revert optimistic update on error
913
- queryClient.invalidateQueries(['connectors']);
914
- console.error('Action failed:', error);
915
- }
916
- };
917
-
918
- // ... rest of component
919
- }
920
- ```
921
-
922
- ### Custom Styling with CSS-in-JS
923
-
924
- ```tsx
925
- import { useConnectors } from '@fastn-ai/react-core';
926
- import styled from 'styled-components';
927
-
928
- const ConnectorGrid = styled.div`
929
- display: grid;
930
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
931
- gap: 1.5rem;
932
- padding: 1rem;
933
- `;
934
-
935
- const ConnectorCard = styled.div`
936
- background: white;
937
- border: 1px solid #e5e7eb;
938
- border-radius: 0.5rem;
939
- padding: 1.5rem;
940
- transition: all 0.2s ease;
941
-
942
- &:hover {
943
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
944
- transform: translateY(-2px);
945
- }
946
- `;
947
-
948
- const ActionButton = styled.button<{ variant: 'primary' | 'secondary' | 'danger' }>`
949
- padding: 0.5rem 1rem;
950
- border-radius: 0.375rem;
951
- font-weight: 500;
952
- transition: all 0.2s ease;
953
-
954
- ${({ variant }) => {
955
- switch (variant) {
956
- case 'primary':
957
- return `
958
- background: #3b82f6;
959
- color: white;
960
- &:hover { background: #2563eb; }
961
- `;
962
- case 'secondary':
963
- return `
964
- background: #6b7280;
965
- color: white;
966
- &:hover { background: #4b5563; }
967
- `;
968
- case 'danger':
969
- return `
970
- background: #dc2626;
971
- color: white;
972
- &:hover { background: #b91c1c; }
973
- `;
974
- }
975
- }}
976
- `;
977
-
978
- function StyledConnectorsList() {
979
- const { data: connectors, isLoading, error } = useConnectors();
980
-
981
- if (isLoading) return <div>Loading...</div>;
982
- if (error) return <div>Error: {error.message}</div>;
983
-
984
- return (
985
- <ConnectorGrid>
986
- {connectors?.map(connector => (
987
- <ConnectorCard key={connector.id}>
988
- <h3>{connector.name}</h3>
989
- <p>{connector.description}</p>
990
- <div>
991
- {connector.actions.map((action, index) => (
992
- <ActionButton
993
- key={index}
994
- variant={
995
- action.actionType === 'ACTIVATION' ? 'primary' :
996
- action.actionType === 'DEACTIVATION' ? 'danger' : 'secondary'
997
- }
998
- onClick={() => action.onClick?.()}
999
- >
1000
- {action.name}
1001
- </ActionButton>
1002
- ))}
1003
- </div>
1004
- </ConnectorCard>
1005
- ))}
1006
- </ConnectorGrid>
1007
- );
1008
- }
1009
- ```
1010
-
1011
- ## 🎯 Best Practices
1012
-
1013
- ### 1. Error Boundaries
1014
-
1015
- Wrap your Fastn components with error boundaries to handle unexpected errors gracefully:
1016
-
1017
- ```tsx
1018
- import { ErrorBoundary } from 'react-error-boundary';
1019
-
1020
- function ErrorFallback({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) {
1021
- return (
1022
- <div className="text-center p-6">
1023
- <h2 className="text-lg font-semibold text-gray-900">Something went wrong</h2>
1024
- <p className="text-gray-600 mt-2">{error.message}</p>
1025
- <button
1026
- onClick={resetErrorBoundary}
1027
- className="mt-4 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"
1028
- >
1029
- Try again
1030
- </button>
1031
- </div>
1032
- );
1033
- }
1034
-
1035
- function App() {
1036
- return (
1037
- <FastnProvider config={config}>
1038
- <ErrorBoundary FallbackComponent={ErrorFallback}>
1039
- <YourApp />
1040
- </ErrorBoundary>
1041
- </FastnProvider>
1042
- );
1043
- }
1044
- ```
1045
-
1046
- ### 2. Loading States
1047
-
1048
- Always provide meaningful loading states for better user experience:
1049
-
1050
- ```tsx
1051
- function LoadingSkeleton() {
1052
- return (
1053
- <div className="animate-pulse">
1054
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
1055
- {[...Array(6)].map((_, i) => (
1056
- <div key={i} className="border rounded-lg p-6">
1057
- <div className="flex items-center space-x-4">
1058
- <div className="w-12 h-12 bg-gray-200 rounded-lg"></div>
1059
- <div className="flex-1">
1060
- <div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
1061
- <div className="h-3 bg-gray-200 rounded w-1/2"></div>
1062
- </div>
1063
- </div>
1064
- <div className="mt-4 flex space-x-2">
1065
- <div className="h-8 bg-gray-200 rounded w-20"></div>
1066
- <div className="h-8 bg-gray-200 rounded w-20"></div>
1067
- </div>
1068
- </div>
1069
- ))}
1070
- </div>
1071
- </div>
1072
- );
1073
- }
1074
- ```
1075
-
1076
- ### 3. Type Safety
1077
-
1078
- Always use TypeScript interfaces for better type safety:
1079
-
1080
- ```tsx
1081
- import type {
1082
- Connector,
1083
- Configuration,
1084
- ConfigurationForm,
1085
- ConnectorField
1086
- } from '@fastn-ai/react-core';
1087
-
1088
- interface ConnectorCardProps {
1089
- connector: Connector;
1090
- onAction?: (action: ConnectorAction) => Promise<void>;
1091
- className?: string;
1092
- }
1093
-
1094
- interface ConfigurationFormProps {
1095
- form: ConfigurationForm;
1096
- onSubmit: (data: Record<string, any>) => Promise<void>;
1097
- onCancel: () => void;
1098
- loading?: boolean;
1099
- }
1100
- ```
1101
-
1102
- ### 4. Performance Optimization
1103
-
1104
- Use React.memo and useMemo for expensive operations:
1105
-
1106
- ```tsx
1107
- import { memo, useMemo } from 'react';
1108
-
1109
- const ConnectorCard = memo(({ connector, onAction }: ConnectorCardProps) => {
1110
- const statusColor = useMemo(() => {
1111
- return connector.status === 'ACTIVE' ? 'green' : 'gray';
1112
- }, [connector.status]);
1113
-
1114
- const actionButtons = useMemo(() => {
1115
- return connector.actions.map((action, index) => (
1116
- <button
1117
- key={index}
1118
- onClick={() => onAction?.(action)}
1119
- className={`px-4 py-2 rounded-md ${
1120
- action.actionType === 'ACTIVATION'
1121
- ? 'bg-blue-600 text-white'
1122
- : 'bg-gray-600 text-white'
1123
- }`}
1124
- >
1125
- {action.name}
1126
- </button>
1127
- ));
1128
- }, [connector.actions, onAction]);
1129
-
1130
- return (
1131
- <div className="border rounded-lg p-6">
1132
- <h3>{connector.name}</h3>
1133
- <p>{connector.description}</p>
1134
- <div className="flex space-x-2 mt-4">
1135
- {actionButtons}
1136
- </div>
1137
- </div>
1138
- );
1139
- });
1140
-
1141
- ConnectorCard.displayName = 'ConnectorCard';
1142
- ```
1143
-
1144
- ## 🐛 Troubleshooting
1145
-
1146
- ### Common Issues
1147
-
1148
- #### 1. Provider Not Found Error
1149
-
1150
- **Error**: `Initialize Fastn with FastnProvider first`
1151
-
1152
- **Solution**: Make sure your component is wrapped with `FastnProvider`:
1153
-
1154
- ```tsx
1155
- // ❌ Wrong - Component outside provider
1156
- function MyComponent() {
1157
- const { data } = useConnectors(); // This will throw an error
1158
- return <div>...</div>;
1159
- }
1160
-
1161
- // ✅ Correct - Component inside provider
1162
- function App() {
1163
- return (
1164
- <FastnProvider config={config}>
1165
- <MyComponent />
1166
- </FastnProvider>
1167
- );
1168
- }
1169
- ```
1170
-
1171
- #### 2. Authentication Errors
1172
-
1173
- **Error**: `401 Unauthorized` or `403 Forbidden`
1174
-
1175
- **Solution**: Check your configuration:
1176
-
1177
- ```tsx
1178
- const config = {
1179
- authToken: 'your-valid-auth-token', // Make sure this is valid
1180
- tenantId: 'your-tenant-id',
1181
- spaceId: 'your-space-id',
1182
- // ... other config
1183
- };
1184
- ```
1185
-
1186
- #### 3. Network Errors
1187
-
1188
- **Error**: `Network Error` or `Failed to fetch`
1189
-
1190
- **Solution**: Check your base URL and network connectivity:
1191
-
1192
- ```tsx
1193
- const config = {
1194
- baseUrl: 'https://api.fastn.ai', // Make sure this is correct
1195
- // ... other config
1196
- };
1197
- ```
1198
-
1199
- #### 4. TypeScript Errors
1200
-
1201
- **Error**: Type mismatches or missing types
1202
-
1203
- **Solution**: Import types explicitly:
1204
-
1205
- ```tsx
1206
- import type {
1207
- Connector,
1208
- Configuration,
1209
- ConnectorAction,
1210
- ConfigurationAction
1211
- } from '@fastn-ai/react-core';
1212
- ```
1213
-
1214
- ### Debug Mode
1215
-
1216
- Enable debug logging to troubleshoot issues:
1217
-
1218
- ```tsx
1219
- // Add this to your app to see detailed logs
1220
- if (process.env.NODE_ENV === 'development') {
1221
- console.log('Fastn Config:', config);
1222
- }
1223
- ```
1224
-
1225
- ## 📚 Additional Resources
1226
-
1227
- - [Fastn AI Documentation](https://docs.fastn.ai)
1228
- - [React Query Documentation](https://tanstack.com/query/latest)
1229
- - [TypeScript Handbook](https://www.typescriptlang.org/docs/)
1230
-
1231
- ## 🤝 Contributing
1232
-
1233
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
1234
-
1235
- ## 📄 License
1236
-
1237
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1238
-
1239
- ## 🆘 Support
1240
-
1241
- If you need help or have questions:
1
+ # @fastn-ai/react-core
1242
2
 
1243
- - 📧 Email: [support@fastn.ai](mailto:support@fastn.ai)
1244
- - 📖 Documentation: [docs.fastn.ai](https://docs.fastn.ai)
1245
- - 🐛 Issues: [GitHub Issues](https://github.com/fastn-ai/react-core/issues)
3
+ A professional React library for integrating Fastn AI connectors into your application. This package provides robust hooks and TypeScript types to manage connectors, configurations, and dynamic configuration forms, empowering you to build custom UIs on top of Fastn's data and logic.
1246
4
 
1247
5
  ---
1248
6
 
1249
- Made with ❤️ by the Fastn team -->
1250
-
1251
-
1252
- # @fastn-ai/react-core
1253
-
1254
- A React library for integrating Fastn AI connectors into your application. This package provides hooks and types to manage connectors, configurations, and dynamic configuration forms, allowing you to build your own UI on top of Fastn's data and logic.
1255
-
1256
7
  ## Features
1257
8
 
1258
- - Manage and list connectors
9
+ - List and manage connectors
1259
10
  - Handle connector configurations
1260
11
  - Render and submit dynamic configuration forms
1261
12
  - Powered by React Query (supports custom or existing clients)
1262
13
 
14
+ ---
15
+
1263
16
  ## Installation
1264
17
 
1265
18
  ```bash
@@ -1268,17 +21,19 @@ npm install @fastn-ai/react-core
1268
21
 
1269
22
  ### Peer Dependencies
1270
23
 
1271
- You need React 18+ and React Query. Install if not already present:
24
+ Ensure you have React 18+ and React Query installed:
1272
25
 
1273
26
  ```bash
1274
27
  npm install react react-dom @tanstack/react-query
1275
28
  ```
1276
29
 
1277
- ## Initialization
30
+ ---
31
+
32
+ ## Getting Started
1278
33
 
1279
34
  ### 1. Basic Setup
1280
35
 
1281
- Wrap your app with `FastnProvider` and provide your configuration. This will create its own React Query client by default.
36
+ Wrap your app with `FastnProvider` and provide your configuration. By default, this creates its own React Query client.
1282
37
 
1283
38
  ```tsx
1284
39
  import { FastnProvider } from "@fastn-ai/react-core";
@@ -1321,11 +76,13 @@ function App() {
1321
76
  }
1322
77
  ```
1323
78
 
79
+ ---
80
+
1324
81
  ## Usage
1325
82
 
1326
83
  ### 1. Fetching Connectors
1327
84
 
1328
- Use the `useConnectors` hook to get the list of available connectors. You can render them however you like.
85
+ Use the `useConnectors` hook to retrieve the list of available connectors.
1329
86
 
1330
87
  ```tsx
1331
88
  import { useConnectors } from "@fastn-ai/react-core";
@@ -1374,7 +131,7 @@ interface ConnectorAction {
1374
131
 
1375
132
  interface ConnectorActionResult {
1376
133
  data: unknown;
1377
- status: "SUCCESS" | "ERROR" | "CANCELLED;
134
+ status: "SUCCESS" | "ERROR" | "CANCELLED";
1378
135
  }
1379
136
 
1380
137
  enum ConnectorActionType {
@@ -1384,6 +141,8 @@ enum ConnectorActionType {
1384
141
  }
1385
142
  ```
1386
143
 
144
+ ---
145
+
1387
146
  ### 2. Fetching Configurations
1388
147
 
1389
148
  Use the `useConfigurations` hook to get connector configurations for a given configuration ID.
@@ -1440,9 +199,11 @@ type ConfigurationActionType =
1440
199
  | ConnectorActionType.DELETE;
1441
200
  ```
1442
201
 
202
+ ---
203
+
1443
204
  ### 3. Dynamic Configuration Forms
1444
205
 
1445
- Use the `useConfigurationForm` hook to fetch a configuration form for a connector or configuration. You can render the form fields as you wish.
206
+ Use the `useConfigurationForm` hook to fetch a configuration form for a connector or configuration. Render the form fields as needed.
1446
207
 
1447
208
  ```tsx
1448
209
  import { useConfigurationForm } from "@fastn-ai/react-core";
@@ -1456,8 +217,7 @@ function ConfigurationForm({ configurationId }: { configurationId: string }) {
1456
217
  return (
1457
218
  <form onSubmit={/* your submit handler */}>
1458
219
  {configurationForm.fields.map((field) => (
1459
- // Render your input based on field.type and field.options
1460
- <div key={field.name}>{field.label}</div>
220
+ // Render field based on type (see below for select and Google Drive picker fields)
1461
221
  ))}
1462
222
  <button type="submit">Submit</button>
1463
223
  </form>
@@ -1486,13 +246,13 @@ interface ConnectorField {
1486
246
  }
1487
247
  ```
1488
248
 
1489
- ### 4. Select Fields and Google Drive File Picker (Advanced Field Handling)
249
+ ---
1490
250
 
1491
- Handling dynamic select fields and Google Drive file pickers is the most advanced part of integrating Fastn AI connectors. This section provides a comprehensive guide to implementing these fields in your own UI.
251
+ ### 4. Advanced Field Handling: Select & Google Drive File Picker
1492
252
 
1493
253
  #### Select Fields (Single & Multi-Select)
1494
254
 
1495
- For fields of type `select` or `multi-select`, use the `useFieldOptions` hook to fetch options, handle search, pagination, and errors. This hook abstracts away the complexity of loading options from remote sources, searching, and paginating.
255
+ For fields of type `select` or `multi-select`, use the `useFieldOptions` hook to fetch options, handle search, pagination, and errors. This abstracts away the complexity of loading options from remote sources.
1496
256
 
1497
257
  **Example: Generic SelectField Component**
1498
258
 
@@ -1500,7 +260,6 @@ For fields of type `select` or `multi-select`, use the `useFieldOptions` hook to
1500
260
  import { useFieldOptions } from "@fastn-ai/react-core";
1501
261
 
1502
262
  function SelectField({ field, value, onChange, isMulti = false }) {
1503
- // useFieldOptions provides all logic for options, loading, errors, search, pagination, etc.
1504
263
  const {
1505
264
  options,
1506
265
  loading,
@@ -1513,17 +272,14 @@ function SelectField({ field, value, onChange, isMulti = false }) {
1513
272
  totalLoadedOptions,
1514
273
  } = useFieldOptions(field);
1515
274
 
1516
- // Handle search input
1517
275
  function handleInputChange(e) {
1518
276
  search(e.target.value);
1519
277
  }
1520
278
 
1521
- // Handle loading more options (pagination)
1522
279
  function handleLoadMore() {
1523
280
  if (hasNext && !loadingMore) loadMore();
1524
281
  }
1525
282
 
1526
- // Handle refresh on error
1527
283
  function handleRefresh() {
1528
284
  refresh();
1529
285
  }
@@ -1572,12 +328,8 @@ function SelectField({ field, value, onChange, isMulti = false }) {
1572
328
  }
1573
329
  ```
1574
330
 
1575
- **Key Points:**
1576
- - `useFieldOptions(field)` returns all logic for options, loading, errors, search, pagination, and refresh.
1577
- - Use `search` for filtering options, `loadMore` for pagination, and `refresh` to retry on error.
1578
- - The component above is generic and can be styled or replaced with any UI library (e.g., react-select).
1579
-
1580
331
  **useFieldOptions Return Type:**
332
+
1581
333
  ```ts
1582
334
  interface UseFieldOptionsReturn {
1583
335
  options: Array<{ label: string; value: string }>;
@@ -1594,21 +346,16 @@ interface UseFieldOptionsReturn {
1594
346
 
1595
347
  #### Google Drive File Picker Field
1596
348
 
1597
- For fields that require Google Drive file selection, the field type will be something like `select_with_google_files_picker` or similar. These fields provide an `optionsSource` with a method to open the Google Files Picker dialog.
349
+ For fields requiring Google Drive file selection, the field type will be `google-files-picker-select` or similar. These fields provide an `optionsSource` with a method to open the Google Files Picker dialog.
1598
350
 
1599
351
  **Example: GoogleFilesPickerField Component**
1600
352
 
1601
353
  ```tsx
1602
354
  function GoogleFilesPickerField({ field, value, onChange, isMulti = false }) {
1603
- // This field may also use useFieldOptions for listing previously picked files
1604
- const { options, loading, error, refresh } = useFieldOptions(field);
1605
-
1606
- // Handler to open Google Drive picker
1607
355
  async function handlePickFiles() {
1608
356
  if (field.optionsSource?.openGoogleFilesPicker) {
1609
357
  await field.optionsSource.openGoogleFilesPicker({
1610
358
  onComplete: async (files) => {
1611
- // files is an array of selected file objects
1612
359
  if (isMulti) {
1613
360
  onChange(files);
1614
361
  } else {
@@ -1616,7 +363,6 @@ function GoogleFilesPickerField({ field, value, onChange, isMulti = false }) {
1616
363
  }
1617
364
  },
1618
365
  onError: async (pickerError) => {
1619
- // Handle picker error (optional)
1620
366
  alert('Google Files Picker error: ' + pickerError);
1621
367
  },
1622
368
  });
@@ -1626,13 +372,7 @@ function GoogleFilesPickerField({ field, value, onChange, isMulti = false }) {
1626
372
  return (
1627
373
  <div>
1628
374
  <label>{field.label}{field.required && ' *'}</label>
1629
- {error && (
1630
- <div>
1631
- <span style={{ color: 'red' }}>Error loading files</span>
1632
- <button type="button" onClick={refresh}>Retry</button>
1633
- </div>
1634
- )}
1635
- <button type="button" onClick={handlePickFiles} disabled={loading}>
375
+ <button type="button" onClick={handlePickFiles}>
1636
376
  Pick from Google Drive
1637
377
  </button>
1638
378
  {value && (
@@ -1640,7 +380,7 @@ function GoogleFilesPickerField({ field, value, onChange, isMulti = false }) {
1640
380
  <strong>Selected file{isMulti ? 's' : ''}:</strong>
1641
381
  <ul>
1642
382
  {(isMulti ? value : [value]).map((file, idx) => (
1643
- <li key={file.id || idx}>{file.name || file.id}</li>
383
+ <li key={file.value || idx}>{file.label || file.value}</li>
1644
384
  ))}
1645
385
  </ul>
1646
386
  </div>
@@ -1651,13 +391,8 @@ function GoogleFilesPickerField({ field, value, onChange, isMulti = false }) {
1651
391
  }
1652
392
  ```
1653
393
 
1654
- **Key Points:**
1655
- - The `openGoogleFilesPicker` method is provided on the field's `optionsSource` property.
1656
- - The `onComplete` callback receives the selected files (array of file objects).
1657
- - You can display the selected files as you wish.
1658
- - This field can be used in both single and multi-select modes.
1659
-
1660
394
  **GoogleFilesPicker Return Type:**
395
+
1661
396
  ```ts
1662
397
  interface UseGoogleFilesPickerReturn {
1663
398
  options: Array<{ label: string; value: string }>;
@@ -1673,19 +408,21 @@ interface UseGoogleFilesPickerReturn {
1673
408
  }
1674
409
  ```
1675
410
 
1676
- **Integrating with Forms**
1677
- - For both SelectField and GoogleFilesPickerField, you can use them as controlled components in your form library (Formik, React Hook Form, etc.).
1678
- - The value and onChange props should be managed by your form state.
1679
- - You can combine these fields with other input types to build a complete dynamic configuration form.
411
+ **Integration Tips:**
412
+ - Use these fields as controlled components in your form library (Formik, React Hook Form, etc.).
413
+ - Manage `value` and `onChange` via your form state.
414
+ - Combine with other input types to build a complete dynamic configuration form.
1680
415
 
1681
416
  ---
1682
417
 
1683
418
  ## Error Handling & Troubleshooting
1684
419
 
1685
- - **Provider Not Found**: Ensure your components are wrapped in `FastnProvider`.
1686
- - **Authentication Errors**: Check your `authToken`, `tenantId`, and `spaceId`.
1687
- - **Network Errors**: Verify your network and API base URL.
1688
- - **TypeScript Errors**: Import types from the package as needed.
420
+ - **Provider Not Found:** Ensure your components are wrapped in `FastnProvider`.
421
+ - **Authentication Errors:** Check your `authToken`, `tenantId`, and `spaceId`.
422
+ - **Network Errors:** Verify your network and API base URL.
423
+ - **TypeScript Errors:** Import types from the package as needed.
424
+
425
+ ---
1689
426
 
1690
427
  ## TypeScript Support
1691
428
 
@@ -1695,16 +432,19 @@ All hooks and data structures are fully typed. You can import types directly:
1695
432
  import type { Connector, Configuration, ConnectorAction, ConfigurationAction } from '@fastn-ai/react-core';
1696
433
  ```
1697
434
 
435
+ ---
436
+
1698
437
  ## License
1699
438
 
1700
439
  MIT License. See the [LICENSE](LICENSE) file for details.
1701
440
 
441
+ ---
442
+
1702
443
  ## Support
1703
444
 
1704
445
  - Email: support@fastn.ai
1705
- - Documentation: https://docs.fastn.ai
1706
- - Issues: https://github.com/fastn-ai/react-core/issues
446
+ - Documentation: [https://docs.fastn.ai](https://docs.fastn.ai)
1707
447
 
1708
448
  ---
1709
449
 
1710
- This package provides the data and logic for Fastn AI connectors. You are free to build your own UI and integrate these hooks and types as needed for your application.
450
+ This package provides the data and logic for Fastn AI connectors. You are free to build your own UI and integrate these hooks and types as needed for your application.