@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
Markdown
# @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.