# Feature: File Upload API

File upload API package that provides programmatic APIs for uploading files to Salesforce with progress tracking and ContentVersion integration. This package serves as a **reference implementation** with APIs that allow you to **build your own custom UI**.

## Package Purpose

This package exposes APIs only - **no components or hooks are exported**. You can:

- Use the `upload()` API to handle the complete upload flow
- Build your own custom UI with full control over the user experience
- Track upload progress for each file in real-time
- Optionally create ContentVersion records linked to Salesforce records

The included components in the source code are for demonstration and testing purposes only.

## Exports

### API Functions

- **upload** – Unified upload API that handles config, upload with progress tracking, and optionally creates ContentVersion records
- **createContentVersion** – Manually create a ContentVersion record from a contentBodyId
- **getCurrentUserId** – Get the current user's Salesforce ID

### Types

- **UploadOptions** – Configuration options for the upload function
- **FileUploadResult** – Result object containing file info and IDs
- **FileUploadProgress** – Progress callback data for tracking upload status
- **UploadStatus** – Upload status enum: "pending" | "uploading" | "processing" | "success" | "error"

## Usage Guide

### Basic Upload with Progress Tracking

Upload files to Salesforce with real-time progress tracking for each file:

```tsx
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";

async function handleUpload(files: File[]) {
  const results = await upload({
    files,
    onProgress: (progress) => {
      console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
      // Update your UI with progress.status and progress.progress
    },
  });

  console.log("Upload complete:", results);
  // results[0].contentBodyId: "069..." (always available)
  // results[0].contentVersionId: undefined (no record linked)
}
```

### Upload with Record Linking

Link uploaded files to a Salesforce record immediately by providing `recordId`:

```tsx
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";

async function uploadToRecord(files: File[], recordId: string) {
  const results = await upload({
    files,
    recordId, // Links files to this record (Account, Opportunity, etc.)
    onProgress: (progress) => {
      // Track progress: pending → uploading → processing → success
      updateUI(progress.fileName, progress.status, progress.progress);
    },
  });

  // Both contentBodyId and contentVersionId are available
  console.log("Uploaded to record:", results);
}
```

### Deferred Record Linking (Record Creation Pattern)

**When creating a new record**: Upload files first without `recordId`, then link them after the record is created:

```tsx
import {
  upload,
  createContentVersion,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";

async function createRecordWithFiles(formData: any, files: File[]) {
  // Step 1: Upload files (no recordId yet)
  const uploadResults = await upload({
    files,
    onProgress: (progress) => console.log(progress),
  });

  // Step 2: Create the record
  const newRecordId = await createRecord(formData);

  // Step 3: Link uploaded files to the new record
  for (const file of uploadResults) {
    const contentVersionId = await createContentVersion(
      new File([""], file.fileName),
      file.contentBodyId,
      newRecordId,
    );
    console.log("ContentVersion created:", contentVersionId);
  }
}
```

### Cancel Upload

Use an AbortController to cancel uploads:

```tsx
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";

function UploadComponent() {
  const abortController = new AbortController();

  const handleUpload = async (files: File[]) => {
    try {
      await upload({
        files,
        signal: abortController.signal,
        onProgress: (progress) => console.log(progress),
      });
    } catch (error) {
      console.error("Upload failed or cancelled:", error);
    }
  };

  const cancelUpload = () => {
    abortController.abort();
  };

  return (
    <div>
      <button onClick={() => handleUpload(selectedFiles)}>Upload</button>
      <button onClick={cancelUpload}>Cancel</button>
    </div>
  );
}
```

## API Reference

### upload(options)

Unified upload API that handles the complete upload flow with progress tracking.

**Parameters:**

```typescript
interface UploadOptions {
  /** Files to upload */
  files: File[];

  /**
   * Record ID to link files to (FirstPublishLocationId).
   * - If provided: Creates ContentVersion and links to this record
   * - If null/undefined: Only uploads file, returns contentBodyId only
   */
  recordId?: string | null;

  /** Callback for upload progress of each file */
  onProgress?: (progress: FileUploadProgress) => void;

  /** Optional abort signal to cancel all uploads */
  signal?: AbortSignal;
}

interface FileUploadProgress {
  fileName: string;
  status: "pending" | "uploading" | "processing" | "success" | "error";
  progress: number; // 0-100 for uploading, 0 for other states
  error?: string;
}
```

**Returns:** `Promise<FileUploadResult[]>`

```typescript
interface FileUploadResult {
  fileName: string;
  size: number;
  contentBodyId: string; // Always available
  contentVersionId?: string; // Only if recordId was provided
}
```

### createContentVersion(file, contentBodyId, recordId)

Manually create a ContentVersion record from a previously uploaded file.

**Parameters:**

- `file` (File): The file object (used for metadata like name)
- `contentBodyId` (string): The ContentBody ID from a previous upload
- `recordId` (string): The record ID for FirstPublishLocationId

**Returns:** `Promise<string | undefined>` - The ContentVersion ID if successful

```tsx
const contentVersionId = await createContentVersion(file, "069xx000000xxxx", "001xx000000yyyy");
```

### getCurrentUserId()

Get the current user's Salesforce ID.

**Returns:** `Promise<string>` - The current user ID

```tsx
const userId = await getCurrentUserId();
```

## Building Your Own UI

This package provides the backend APIs - you can build any UI you want. Here's a basic example:

```tsx
import {
  upload,
  type FileUploadProgress,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
import { useState } from "react";

function CustomFileUpload({ recordId }: { recordId?: string }) {
  const [progress, setProgress] = useState<Map<string, FileUploadProgress>>(new Map());

  const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files || []);

    await upload({
      files,
      recordId,
      onProgress: (fileProgress) => {
        setProgress((prev) => new Map(prev).set(fileProgress.fileName, fileProgress));
      },
    });
  };

  return (
    <div>
      <input type="file" multiple onChange={handleFileSelect} />

      {Array.from(progress.entries()).map(([fileName, fileProgress]) => (
        <div key={fileName}>
          {fileName}: {fileProgress.status} - {fileProgress.progress}%
          {fileProgress.error && <span>Error: {fileProgress.error}</span>}
        </div>
      ))}
    </div>
  );
}
```

## Agent/Vibe Integration Guide

When building file upload functionality with an AI agent like Vibe, follow this pattern:

### 1. Ask User for Record Context

```
Agent: "Do you want to link uploaded files to a specific record, or upload them first and link later?"

Options:
A) Link to existing record (provide recordId)
B) Upload only (link later with createContentVersion)
C) Link to current user
```

### 2. Implement Based on Response

**Option A: Link to existing record**

```tsx
await upload({ files, recordId: "001xx000000yyyy" });
```

**Option B: Upload only, link later**

```tsx
const results = await upload({ files }); // No recordId
// Later: await createContentVersion(file, results[0].contentBodyId, newRecordId);
```

**Option C: Link to current user**

```tsx
const userId = await getCurrentUserId();
await upload({ files, recordId: userId });
```

### 3. Track Progress in Your UI

Always provide progress feedback to users:

```tsx
onProgress: (progress) => {
  // Update your UI based on:
  // - progress.fileName
  // - progress.status (pending, uploading, processing, success, error)
  // - progress.progress (0-100)
  // - progress.error (if status is 'error')
};
```

## Skills

> [https://docs.cline.bot/features/skills](https://docs.cline.bot/features/skills)

The sample includes a **skill** to show how a feature can bundle an on-demand workflow.

Copy the `skills/implementing-file-upload/` folder into `.cline/skills/` (Cline) or `.cursor/skills/` (Cursor) to try it, or use it as a template for your own skills.

## Dependencies

- **@salesforce/ui-bundle** – For API client and Salesforce integration
- **@salesforce/platform-sdk** – For data SDK integration

## Reference Implementation

This package includes a reference implementation with UI components for demonstration purposes. The components are located in `src/features/fileupload/` and include:

- FileUpload component with drop zone and progress dialog
- useFileUpload hook for custom implementations
- Test pages showing various usage patterns

These are **not exported** from the package but can be viewed as examples of how to build your own UI using the APIs.

## Testing

Test the reference implementation locally:

```bash
npx nx dev @salesforce/ui-bundle-template-feature-react-file-upload
```

Open the app and navigate to the test pages to see the APIs in action.
