npm package: @srfoster/one-backend-react
Description: React components and hooks for One Backend authentication and data management
Author: Your Name
Latest version: 1.2.1

# TODO

* Security: CORS per app id?  Distinct auth/JWT per app login.  Prevent one frontend from CRUDing another's data

NOTE: This is a WIP, not recommended for use unless you're me...


# One Backend React

React components and hooks for seamless integration with One Backend - a generalized serverless backend for authentication, CRUD operations, and file management.

## Installation

```bash
npm install @srfoster/one-backend-react
```

## Quick Start

### 1. Get Your App ID

Your App ID is a unique identifier for your application in the One Backend system. You can use any string that uniquely identifies your app, such as:

- `my-todo-app`
- `acme-crm`
- `blog-platform`
- `ecommerce-store`

**Important:** Choose a unique App ID as this isolates your app's data from other applications using the same backend. All users, data records, and files will be scoped to your App ID.

### 2. Setup the Provider

Wrap your app with the `AuthProvider`:

```tsx
import React from 'react';
import { AuthProvider } from '@srfoster/one-backend-react';

function App() {
  return (
    <AuthProvider config={{
      appId: 'your-app-id'
    }}>
      <YourAppContent />
    </AuthProvider>
  );
}
```

### 2. Use Authentication

```tsx
import React from 'react';
import { useAuth, LoginForm, RegisterForm } from '@srfoster/one-backend-react';

function AuthPage() {
  const { user, logout, isAuthenticated } = useAuth();

  if (isAuthenticated) {
    return (
      <div>
        <h1>Welcome, {user?.email}!</h1>
        <button onClick={logout}>Logout</button>
      </div>
    );
  }

  return (
    <div>
      <h2>Login</h2>
      <LoginForm 
        onSuccess={() => console.log('Logged in!')}
        onError={(error) => console.error(error)}
      />
      
      <h2>Register</h2>
      <RegisterForm 
        onSuccess={() => console.log('Registered!')}
        onError={(error) => console.error(error)}
      />
    </div>
  );
}
```

### 3. Manage Data

```tsx
import React from 'react';
import { useData } from '@srfoster/one-backend-react';

function PostsList() {
  const { 
    data: posts, 
    loading, 
    error, 
    createRecord, 
    updateRecord, 
    deleteRecord 
  } = useData('posts');

  const handleCreatePost = async () => {
    await createRecord({
      data: {
        title: 'New Post',
        content: 'This is a new post',
        status: 'published'
      }
    });
  };

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <button onClick={handleCreatePost}>Create Post</button>
      {posts.map(post => (
        <div key={post.id}>
          <h3>{post.data.title}</h3>
          <p>{post.data.content}</p>
          <button onClick={() => deleteRecord(post.id)}>Delete</button>
        </div>
      ))}
    </div>
  );
}
```

#### Batch Delete Records

```tsx
import React, { useState } from 'react';
import { useData } from '@srfoster/one-backend-react';

function PostsManager() {
  const { 
    data: posts, 
    loading, 
    error, 
    deleteRecords 
  } = useData('posts');
  
  const [selectedPosts, setSelectedPosts] = useState<string[]>([]);

  const handleBatchDelete = async () => {
    if (selectedPosts.length === 0) return;
    
    const result = await deleteRecords(selectedPosts);
    if (result.success) {
      console.log(`Deleted ${result.deleted} posts`);
      if (result.failed > 0) {
        console.log(`Failed to delete ${result.failed} posts`);
      }
      setSelectedPosts([]); // Clear selection
    }
  };

  const toggleSelection = (postId: string) => {
    setSelectedPosts(prev => 
      prev.includes(postId) 
        ? prev.filter(id => id !== postId)
        : [...prev, postId]
    );
  };

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <div>
        <button 
          onClick={handleBatchDelete}
          disabled={selectedPosts.length === 0}
        >
          Delete Selected ({selectedPosts.length})
        </button>
      </div>
      
      {posts.map(post => (
        <div key={post.id}>
          <input
            type="checkbox"
            checked={selectedPosts.includes(post.id)}
            onChange={() => toggleSelection(post.id)}
          />
          <h3>{post.data.title}</h3>
          <p>{post.data.content}</p>
        </div>
      ))}
    </div>
  );
}
```

### 4. Handle File Uploads

```tsx
import React from 'react';
import { useFileUpload } from '@srfoster/one-backend-react';

function FileUploader() {
  const { uploadFile, uploading, error } = useFileUpload();

  const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;

    const result = await uploadFile(file);
    if (result) {
      console.log('File uploaded:', result.fileId);
    }
  };

  return (
    <div>
      <input type="file" onChange={handleFileUpload} disabled={uploading} />
      {uploading && <p>Uploading...</p>}
      {error && <p>Error: {error}</p>}
    </div>
  );
}
```

## API Reference

### Components

#### `AuthProvider`
Provides authentication context to your app.

**Props:**
- `config`: Object with `appId`
- `children`: React nodes

#### `LoginForm`
Pre-built login form component.

**Props:**
- `onSuccess?`: Callback when login succeeds
- `onError?`: Callback when login fails
- `className?`: CSS class name

#### `RegisterForm`
Pre-built registration form component.

**Props:**
- `onSuccess?`: Callback when registration succeeds
- `onError?`: Callback when registration fails
- `className?`: CSS class name
- `showNameFields?`: Whether to show first/last name fields (default: true)

### Hooks

#### `useAuth()`
Provides authentication state and methods.

**Returns:**
- `user`: Current user object or null
- `token`: JWT token or null
- `isLoading`: Boolean indicating loading state
- `isAuthenticated`: Boolean indicating if user is logged in
- `login(email, password)`: Login function
- `register(email, password, firstName?, lastName?)`: Registration function
- `logout()`: Logout function
- `client`: OneBackendClient instance

#### `useData(resourceType)`
Manages CRUD operations for a specific resource type.

**Returns:**
- `data`: Array of records
- `loading`: Boolean indicating loading state
- `error`: Error message or null
- `pagination`: Pagination info
- `fetchData(page?, limit?, filters?)`: Fetch data function
- `createRecord(request)`: Create new record
- `updateRecord(id, request)`: Update existing record
- `deleteRecord(id)`: Delete single record
- `deleteRecords(ids)`: Batch delete multiple records
  - `ids`: Array of record IDs (max 100)
  - Returns: `{ success: boolean, deleted: number, failed: number, details: any[] }`
- `refresh()`: Refresh data

#### `useFileUpload()`
Handles file uploads to S3.

**Returns:**
- `uploadFile(file)`: Upload file function
- `getDownloadUrl(fileId)`: Get download URL
- `deleteFile(fileId)`: Delete file
- `uploading`: Boolean indicating upload state
- `error`: Error message or null

### Client

#### `OneBackendClient`
Direct API client for advanced usage.

```tsx
import { useAuth } from '@srfoster/one-backend-react';

function MyComponent() {
  const { client } = useAuth();
  
  // Use client methods directly
  const handleCustomRequest = async () => {
    const response = await client.getRecords('custom-resource');
    // ...
  };
}
```

## Configuration

### Environment Setup

You just need:

1. **App ID**: A unique identifier for your application (e.g., `my-app-name`)

The API connection is handled automatically by the package - no configuration needed!

### TypeScript Support

This package is written in TypeScript and provides full type definitions.

---

## For One Backend Maintainers

### Updating the API URL

When the One Backend is deployed to a new AWS region or account, the API Gateway URL will change. To update the React package automatically:

1. **Run the automated update script**:
   ```bash
   cd react-package
   npm run update-api-url
   ```
   This script will:
   - Fetch the current API URL from your serverless deployment
   - Automatically update `src/api/client.ts` with the new endpoint
   - Confirm the update was successful

2. **Build and publish the updated package**:
   ```bash
   npm run build
   npm version patch  # or minor/major as appropriate
   npm publish --access public
   ```

### Manual API URL Update (if needed)

If the automated script fails, you can manually update the API URL:

1. **Get the API URL** from your serverless deployment:
   ```bash
   cd /path/to/one-backend
   npx serverless info
   ```
   Look for any endpoint in the output (e.g., `https://abc123xyz.execute-api.us-west-2.amazonaws.com/dev`)

2. **Update the client configuration**:
   Edit `src/api/client.ts` and update the hardcoded URL:
   ```typescript
   private static readonly API_URL = 'https://your-new-endpoint.execute-api.region.amazonaws.com/dev';
   ```

This ensures all React package users automatically connect to the correct backend without needing to configure URLs.

## License

MIT