UNPKG

@dev-fastn-ai/react-core

Version:

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.

450 lines (357 loc) 11.1 kB
# @fastn-ai/react-core A React library for integrating Fastn AI connectors into your application. This package provides robust hooks to manage connectors, configurations, and dynamic configuration forms, empowering you to build custom UIs on top of Fastn's data and logic. --- ## Features - List and manage connectors - Handle connector configurations - Render and submit dynamic configuration forms - Powered by React Query (supports custom or existing clients) --- ## Installation ```bash npm install @fastn-ai/react-core ``` ### Peer Dependencies Ensure you have React 18+ and React Query installed: ```bash npm install react react-dom @tanstack/react-query ``` --- ## Getting Started ### 1. Basic Setup Wrap your app with `FastnProvider` and provide your configuration. By default, this creates its own React Query client. ```tsx import { FastnProvider } from "@fastn-ai/react-core"; const fastnConfig = { environment: "LIVE", // or 'DRAFT' or custom string authToken: "your-auth-token", tenantId: "your-tenant-id", spaceId: "your-space-id", }; function App() { return ( <FastnProvider config={fastnConfig}> {/* Your app components here */} </FastnProvider> ); } ``` ### 2. Using an Existing React Query Client If your app already uses React Query, you can pass your own client: ```tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { FastnProvider } from "@fastn-ai/react-core"; const queryClient = new QueryClient(); const fastnConfig = { /* ... */ }; function App() { return ( <QueryClientProvider client={queryClient}> <FastnProvider config={fastnConfig}> {/* Your app components here */} </FastnProvider> </QueryClientProvider> ); } ``` --- ## Usage ### 1. Fetching Connectors Use the `useConnectors` hook to retrieve the list of available connectors. ```tsx import { useConnectors } from "@fastn-ai/react-core"; function ConnectorsList() { const { data: connectors, isLoading, error } = useConnectors(); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {connectors?.map((connector) => ( <li key={connector.id}>{connector.name}</li> ))} </ul> ); } ``` #### Types ```ts interface Connector { id: string; name: string; description: string; imageUri?: string; status: ConnectorStatus; actions: ConnectorAction[]; } enum ConnectorStatus { ACTIVE = "ACTIVE", INACTIVE = "INACTIVE", ALL = "ALL", } interface ConnectorAction { name: string; actionType: ConnectorActionType | string; form?: ConnectorForm | null; onClick?: (data?: unknown) => Promise<ConnectorActionResult>; onSubmit?: (formData: Record<string, unknown>) => Promise<ConnectorActionResult>; } interface ConnectorActionResult { data: unknown; status: "SUCCESS" | "ERROR" | "CANCELLED"; } enum ConnectorActionType { ACTIVATION = "ACTIVATION", DEACTIVATION = "DEACTIVATION", NONE = "NONE", } ``` --- ### 2. Fetching Configurations Use the `useConfigurations` hook to get connector configurations for a given configuration ID. ```tsx import { useConfigurations } from "@fastn-ai/react-core"; function ConfigurationsList({ configurationId }: { configurationId: string }) { const { data: configurations, isLoading, error } = useConfigurations({ configurationId, status: "ALL", // 'ENABLED' | 'DISABLED' | 'ALL' }); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {configurations?.map((config) => ( <li key={config.id}>{config.name}</li> ))} </ul> ); } ``` #### Types ```ts interface Configuration { id: string; connectorId: string; configurationId: string; name: string; flowId: string; description: string; imageUri: string; status: string; actions: ConfigurationAction[]; metadata?: unknown; } interface ConfigurationAction { name: string; actionType: ConfigurationActionType; onClick?: () => Promise<ConnectorActionResult>; form?: ConnectorForm | null; onSubmit?: (formData: unknown) => Promise<ConnectorActionResult>; } type ConfigurationActionType = | ConnectorActionType.ENABLE | ConnectorActionType.DISABLE | ConnectorActionType.DELETE; ``` --- ### 3. Dynamic Configuration Forms Use the `useConfigurationForm` hook to fetch a configuration form for a connector or configuration. Render the form fields as needed. ```tsx import { useConfigurationForm } from "@fastn-ai/react-core"; function ConfigurationForm({ configurationId }: { configurationId: string }) { const { data: configurationForm, isLoading, error } = useConfigurationForm({ configurationId }); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <form onSubmit={/* your submit handler */}> {configurationForm.fields.map((field) => ( // Render field based on type (see below for select and Google Drive picker fields) ))} <button type="submit">Submit</button> </form> ); } ``` #### Types ```ts interface ConfigurationForm { name: string; description: string; imageUri: string; fields: ConnectorField[]; submitHandler: (formData: unknown) => Promise<void>; } interface ConnectorField { name: string; label: string; type: string; // e.g. 'text', 'select', etc. required?: boolean; options?: Array<{ label: string; value: string }>; // ...other field properties } ``` --- ### 4. Advanced Field Handling: Select & Google Drive File Picker #### Select Fields (Single & Multi-Select) 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. **Example: Generic SelectField Component** ```tsx import { useFieldOptions } from "@fastn-ai/react-core"; function SelectField({ field, value, onChange, isMulti = false }) { const { options, loading, loadingMore, hasNext, loadMore, error, search, refresh, totalLoadedOptions, } = useFieldOptions(field); function handleInputChange(e) { search(e.target.value); } function handleLoadMore() { if (hasNext && !loadingMore) loadMore(); } function handleRefresh() { refresh(); } return ( <div> <label>{field.label}{field.required && ' *'}</label> {error && ( <div> <span style={{ color: 'red' }}>Error loading options</span> <button type="button" onClick={handleRefresh}>Retry</button> </div> )} <input type="text" placeholder={field.placeholder || `Search ${field.label}`} onChange={handleInputChange} disabled={loading} /> <select multiple={isMulti} value={value} onChange={e => { if (isMulti) { const selected = Array.from(e.target.selectedOptions, o => o.value); onChange(selected); } else { onChange(e.target.value); } }} disabled={loading} > {options.map(opt => ( <option key={opt.value} value={opt.value}>{opt.label}</option> ))} </select> {loading && <div>Loading options...</div>} {hasNext && !loadingMore && ( <button type="button" onClick={handleLoadMore}>Load More</button> )} {loadingMore && <div>Loading more...</div>} <div>Loaded {totalLoadedOptions} options{hasNext ? '' : ' (all loaded)'}</div> {field.description && <div style={{ fontSize: 'smaller', color: '#666' }}>{field.description}</div>} </div> ); } ``` **useFieldOptions Return Type:** ```ts interface UseFieldOptionsReturn { options: Array<{ label: string; value: string }>; loading: boolean; loadingMore: boolean; hasNext: boolean; loadMore: () => Promise<void>; error: string | null; search: (query: string) => void; refresh: () => void; totalLoadedOptions: number; } ``` #### Google Drive File Picker Field 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. **Example: GoogleFilesPickerField Component** ```tsx function GoogleFilesPickerField({ field, value, onChange, isMulti = false }) { async function handlePickFiles() { if (field.optionsSource?.openGoogleFilesPicker) { await field.optionsSource.openGoogleFilesPicker({ onComplete: async (files) => { if (isMulti) { onChange(files); } else { onChange(files[0]); } }, onError: async (pickerError) => { alert('Google Files Picker error: ' + pickerError); }, }); } } return ( <div> <label>{field.label}{field.required && ' *'}</label> <button type="button" onClick={handlePickFiles}> Pick from Google Drive </button> {value && ( <div> <strong>Selected file{isMulti ? 's' : ''}:</strong> <ul> {(isMulti ? value : [value]).map((file, idx) => ( <li key={file.value || idx}>{file.label || file.value}</li> ))} </ul> </div> )} {field.description && <div style={{ fontSize: 'smaller', color: '#666' }}>{field.description}</div>} </div> ); } ``` **GoogleFilesPicker Return Type:** ```ts interface UseGoogleFilesPickerReturn { options: Array<{ label: string; value: string }>; loading: boolean; loadingMore: boolean; hasNext: boolean; query: string; refresh: () => Promise<void>; search: (query: string) => void; loadMore: () => Promise<void>; totalLoadedOptions: number; error: string | null; } ``` **Integration Tips:** - Use these fields as controlled components in your form library (Formik, React Hook Form, etc.). - Manage `value` and `onChange` via your form state. - Combine with other input types to build a complete dynamic configuration form. --- ## Error Handling & Troubleshooting - **Provider Not Found:** Ensure your components are wrapped in `FastnProvider`. - **Authentication Errors:** Check your `authToken`, `tenantId`, and `spaceId`. - **Network Errors:** Verify your network and API base URL. - **TypeScript Errors:** Import types from the package as needed. --- ## TypeScript Support All hooks and data structures are fully typed. You can import types directly: ```ts import type { Connector, Configuration, ConnectorAction, ConfigurationAction } from '@fastn-ai/react-core'; ``` --- ## License MIT License. See the [LICENSE](LICENSE) file for details. --- ## Support - Email: support@fastn.ai - Documentation: [https://docs.fastn.ai](https://docs.fastn.ai) --- 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.