@salesforce/webapp-template-feature-react-file-upload-experimental 1.90.1 → 1.90.3
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 +245 -219
- package/dist/CHANGELOG.md +16 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/package.json +3 -3
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{api → features/fileupload/api}/fileUpload.ts +153 -0
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUploadDialog.tsx +2 -2
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUploadFileItem.tsx +1 -1
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts +25 -39
- package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/routes.tsx +2 -2
- package/dist/package.json +1 -1
- package/package.json +14 -9
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/appLayout.tsx +9 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/__inherit__button.tsx +39 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/__inherit__dialog.tsx +102 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/api/fileUpload.ts +299 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/assets/icon-image-close.svg +3 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/assets/image.svg +3 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/assets/success.svg +3 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/assets/symbols.svg +1 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUpload.tsx +100 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUploadDialog.tsx +79 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUploadDropZone.tsx +90 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUploadFileItem.tsx +99 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/components/FileUploadIcons.tsx +90 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/hooks/useFileUpload.ts +312 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/hooks/useFileUploadDialog.ts +70 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/pages/UploadTest.tsx +56 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/types/fileUpload.ts +28 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/utils/fileUploadUtils.ts +54 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/features/fileupload/utils/labels.ts +23 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts +60 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/src/routes.tsx +22 -0
- package/src/force-app/main/default/webapplications/feature-react-file-upload/vite.config.ts +43 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{assets → features/fileupload/assets}/icon-image-close.svg +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{assets → features/fileupload/assets}/image.svg +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{assets → features/fileupload/assets}/success.svg +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{assets → features/fileupload/assets}/symbols.svg +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUpload.tsx +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUploadDropZone.tsx +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{components → features/fileupload/components}/FileUploadIcons.tsx +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{hooks → features/fileupload/hooks}/useFileUpload.ts +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{hooks → features/fileupload/hooks}/useFileUploadDialog.ts +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{pages → features/fileupload/pages}/UploadTest.tsx +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{types → features/fileupload/types}/fileUpload.ts +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{utils → features/fileupload/utils}/fileUploadUtils.ts +0 -0
- /package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/{utils → features/fileupload/utils}/labels.ts +0 -0
package/README.md
CHANGED
|
@@ -1,266 +1,252 @@
|
|
|
1
|
-
# Feature: File Upload
|
|
1
|
+
# Feature: File Upload API
|
|
2
2
|
|
|
3
|
-
File upload
|
|
3
|
+
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**.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
### Components & Hooks
|
|
8
|
-
|
|
9
|
-
- **FileUpload** – Component with OOTB drop zone, dialog, and progress. Props: `accept?`, `multiple?`, `recordId?`, `onUploadComplete?`, `onUploadError?`, `className?`, `dropZoneClassName?`, `formatHint?`, `maxFileSize?`.
|
|
10
|
-
- **useFileUpload** – Headless hook for custom UIs. Returns `getInputProps`, `openFilePicker`, `fileItems`, `getDropZoneProps`, `cancelFile`, `reset`, etc.
|
|
11
|
-
- **UseFileUploadOptions** – Type for the `useFileUpload` options.
|
|
12
|
-
|
|
13
|
-
### API Functions
|
|
14
|
-
|
|
15
|
-
- **createContentVersion** – Manually create a ContentVersion record from a contentBodyId.
|
|
16
|
-
|
|
17
|
-
## Agent Configuration Guide
|
|
5
|
+
## Package Purpose
|
|
18
6
|
|
|
19
|
-
|
|
7
|
+
This package exposes APIs only - **no components or hooks are exported**. You can:
|
|
20
8
|
|
|
21
|
-
|
|
9
|
+
- Use the `upload()` API to handle the complete upload flow
|
|
10
|
+
- Build your own custom UI with full control over the user experience
|
|
11
|
+
- Track upload progress for each file in real-time
|
|
12
|
+
- Optionally create ContentVersion records linked to Salesforce records
|
|
22
13
|
|
|
23
|
-
|
|
24
|
-
- Prompt: "Should users be able to upload multiple files at once?"
|
|
25
|
-
- Type: Boolean (true/false)
|
|
26
|
-
- Default: false (single file only)
|
|
14
|
+
The included components in the source code are for demonstration and testing purposes only.
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
- Prompt: "Do you want to link uploaded files to a specific Salesforce record?"
|
|
30
|
-
- Type: String (Salesforce record ID) or omit
|
|
31
|
-
- Note: If provided, creates ContentVersion and links to record immediately. If omitted, only returns `contentBodyId`.
|
|
32
|
-
- **Record Form Pattern**: If used in a record creation form where the record doesn't exist yet, omit `recordId` during upload, then use `createContentVersion` with the `contentBodyId` and the newly created record ID after form submission.
|
|
33
|
-
|
|
34
|
-
3. **Completion Handler** (`onUploadComplete`):
|
|
35
|
-
- Prompt: "What should happen after files are successfully uploaded?"
|
|
36
|
-
- Type: Function that receives array of uploaded files
|
|
37
|
-
- Required properties to handle: `name`, `size`, `contentBodyId`, `contentVersionId`
|
|
16
|
+
## Exports
|
|
38
17
|
|
|
39
|
-
###
|
|
18
|
+
### API Functions
|
|
40
19
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- Default: All file types accepted
|
|
20
|
+
- **upload** – Unified upload API that handles config, upload with progress tracking, and optionally creates ContentVersion records
|
|
21
|
+
- **createContentVersion** – Manually create a ContentVersion record from a contentBodyId
|
|
22
|
+
- **getCurrentUserId** – Get the current user's Salesforce ID
|
|
45
23
|
|
|
46
|
-
|
|
47
|
-
- Prompt: "What is the maximum file size in MB?"
|
|
48
|
-
- Type: Number (in megabytes)
|
|
49
|
-
- Default: 2048 MB (2 GB)
|
|
24
|
+
### Types
|
|
50
25
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
26
|
+
- **UploadOptions** – Configuration options for the upload function
|
|
27
|
+
- **FileUploadResult** – Result object containing file info and IDs
|
|
28
|
+
- **FileUploadProgress** – Progress callback data for tracking upload status
|
|
29
|
+
- **UploadStatus** – Upload status enum: "pending" | "uploading" | "processing" | "success" | "error"
|
|
55
30
|
|
|
56
|
-
|
|
57
|
-
- Prompt: "Do you need custom CSS classes for the drop zone?"
|
|
58
|
-
- Type: String (CSS class names)
|
|
59
|
-
- Default: Standard styling
|
|
31
|
+
## Usage Guide
|
|
60
32
|
|
|
61
|
-
|
|
62
|
-
- Prompt: "How should upload errors be handled?"
|
|
63
|
-
- Type: Function that receives error information
|
|
64
|
-
- Default: Standard error display
|
|
33
|
+
### Basic Upload with Progress Tracking
|
|
65
34
|
|
|
66
|
-
|
|
67
|
-
- Prompt: "Do you need custom CSS classes for the container?"
|
|
68
|
-
- Type: String (CSS class names)
|
|
69
|
-
- Default: Standard styling
|
|
35
|
+
Upload files to Salesforce with real-time progress tracking for each file:
|
|
70
36
|
|
|
71
|
-
|
|
37
|
+
```tsx
|
|
38
|
+
import { upload } from "@salesforce/webapp-template-feature-react-file-upload-experimental";
|
|
39
|
+
|
|
40
|
+
async function handleUpload(files: File[]) {
|
|
41
|
+
const results = await upload({
|
|
42
|
+
files,
|
|
43
|
+
onProgress: (progress) => {
|
|
44
|
+
console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
|
|
45
|
+
// Update your UI with progress.status and progress.progress
|
|
46
|
+
},
|
|
47
|
+
});
|
|
72
48
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
2. "Do you want to link uploaded files to a specific Salesforce record?" → User: "Yes, to an Account record"
|
|
78
|
-
3. "What file types should be accepted?" → User: "Images and PDFs only"
|
|
79
|
-
4. "What is the maximum file size in MB?" → User: "50 MB"
|
|
80
|
-
5. "What hint text should be displayed to users?" → User: "JPEG, PNG, PDF, up to 50MB"
|
|
81
|
-
6. "How should files be handled after upload?" → User: "Log them to console"
|
|
82
|
-
|
|
83
|
-
Agent generates:
|
|
84
|
-
<FileUpload
|
|
85
|
-
multiple
|
|
86
|
-
accept="image/*,.pdf"
|
|
87
|
-
recordId={accountId}
|
|
88
|
-
maxFileSize={50}
|
|
89
|
-
formatHint="JPEG, PNG, PDF, up to 50MB"
|
|
90
|
-
onUploadComplete={(files) => console.log("Uploaded:", files)}
|
|
91
|
-
/>
|
|
49
|
+
console.log("Upload complete:", results);
|
|
50
|
+
// results[0].contentBodyId: "069..." (always available)
|
|
51
|
+
// results[0].contentVersionId: undefined (no record linked)
|
|
52
|
+
}
|
|
92
53
|
```
|
|
93
54
|
|
|
94
|
-
|
|
55
|
+
### Upload with Record Linking
|
|
95
56
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Use the component as-is for a ready-made drop zone, dialog, and progress display.
|
|
57
|
+
Link uploaded files to a Salesforce record immediately by providing `recordId`:
|
|
99
58
|
|
|
100
59
|
```tsx
|
|
101
|
-
import {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
console.log("Uploaded:", files);
|
|
113
|
-
}}
|
|
114
|
-
/>;
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Option 2: Upload file only (no ContentVersion)
|
|
118
|
-
|
|
119
|
-
Omit `recordId` to only upload the file and get the `contentBodyId`:
|
|
60
|
+
import { upload } from "@salesforce/webapp-template-feature-react-file-upload-experimental";
|
|
61
|
+
|
|
62
|
+
async function uploadToRecord(files: File[], recordId: string) {
|
|
63
|
+
const results = await upload({
|
|
64
|
+
files,
|
|
65
|
+
recordId, // Links files to this record (Account, Opportunity, etc.)
|
|
66
|
+
onProgress: (progress) => {
|
|
67
|
+
// Track progress: pending → uploading → processing → success
|
|
68
|
+
updateUI(progress.fileName, progress.status, progress.progress);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
120
71
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
onUploadComplete={(files) => {
|
|
125
|
-
// Only contentBodyId is returned, no ContentVersion created
|
|
126
|
-
console.log("Content Body ID:", files[0].contentBodyId);
|
|
127
|
-
console.log("Content Version ID:", files[0].contentVersionId); // undefined
|
|
128
|
-
}}
|
|
129
|
-
/>
|
|
72
|
+
// Both contentBodyId and contentVersionId are available
|
|
73
|
+
console.log("Uploaded to record:", results);
|
|
74
|
+
}
|
|
130
75
|
```
|
|
131
76
|
|
|
132
|
-
###
|
|
77
|
+
### Deferred Record Linking (Record Creation Pattern)
|
|
133
78
|
|
|
134
|
-
When
|
|
79
|
+
**When creating a new record**: Upload files first without `recordId`, then link them after the record is created:
|
|
135
80
|
|
|
136
81
|
```tsx
|
|
137
82
|
import {
|
|
138
|
-
|
|
83
|
+
upload,
|
|
139
84
|
createContentVersion,
|
|
140
85
|
} from "@salesforce/webapp-template-feature-react-file-upload-experimental";
|
|
141
86
|
|
|
142
|
-
function
|
|
143
|
-
|
|
87
|
+
async function createRecordWithFiles(formData: any, files: File[]) {
|
|
88
|
+
// Step 1: Upload files (no recordId yet)
|
|
89
|
+
const uploadResults = await upload({
|
|
90
|
+
files,
|
|
91
|
+
onProgress: (progress) => console.log(progress),
|
|
92
|
+
});
|
|
144
93
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
94
|
+
// Step 2: Create the record
|
|
95
|
+
const newRecordId = await createRecord(formData);
|
|
96
|
+
|
|
97
|
+
// Step 3: Link uploaded files to the new record
|
|
98
|
+
for (const file of uploadResults) {
|
|
99
|
+
const contentVersionId = await createContentVersion(
|
|
100
|
+
new File([""], file.fileName),
|
|
101
|
+
file.contentBodyId,
|
|
102
|
+
newRecordId,
|
|
103
|
+
);
|
|
104
|
+
console.log("ContentVersion created:", contentVersionId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Cancel Upload
|
|
110
|
+
|
|
111
|
+
Use an AbortController to cancel uploads:
|
|
150
112
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
113
|
+
```tsx
|
|
114
|
+
import { upload } from "@salesforce/webapp-template-feature-react-file-upload-experimental";
|
|
115
|
+
|
|
116
|
+
function UploadComponent() {
|
|
117
|
+
const abortController = new AbortController();
|
|
118
|
+
|
|
119
|
+
const handleUpload = async (files: File[]) => {
|
|
120
|
+
try {
|
|
121
|
+
await upload({
|
|
122
|
+
files,
|
|
123
|
+
signal: abortController.signal,
|
|
124
|
+
onProgress: (progress) => console.log(progress),
|
|
125
|
+
});
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error("Upload failed or cancelled:", error);
|
|
163
128
|
}
|
|
164
129
|
};
|
|
165
130
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
<input name="name" placeholder="Record Name" />
|
|
170
|
-
|
|
171
|
-
{/* File upload without recordId */}
|
|
172
|
-
<FileUpload multiple onUploadComplete={handleFileUploadComplete} />
|
|
131
|
+
const cancelUpload = () => {
|
|
132
|
+
abortController.abort();
|
|
133
|
+
};
|
|
173
134
|
|
|
174
|
-
|
|
175
|
-
|
|
135
|
+
return (
|
|
136
|
+
<div>
|
|
137
|
+
<button onClick={() => handleUpload(selectedFiles)}>Upload</button>
|
|
138
|
+
<button onClick={cancelUpload}>Cancel</button>
|
|
139
|
+
</div>
|
|
176
140
|
);
|
|
177
141
|
}
|
|
178
142
|
```
|
|
179
143
|
|
|
180
|
-
|
|
144
|
+
## API Reference
|
|
181
145
|
|
|
182
|
-
|
|
146
|
+
### upload(options)
|
|
183
147
|
|
|
184
|
-
|
|
185
|
-
import {
|
|
186
|
-
FileUpload,
|
|
187
|
-
createContentVersion,
|
|
188
|
-
getCurrentUserId,
|
|
189
|
-
} from "@salesforce/webapp-template-feature-react-file-upload-experimental";
|
|
148
|
+
Unified upload API that handles the complete upload flow with progress tracking.
|
|
190
149
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
150
|
+
**Parameters:**
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
interface UploadOptions {
|
|
154
|
+
/** Files to upload */
|
|
155
|
+
files: File[];
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Record ID to link files to (FirstPublishLocationId).
|
|
159
|
+
* - If provided: Creates ContentVersion and links to this record
|
|
160
|
+
* - If null/undefined: Only uploads file, returns contentBodyId only
|
|
161
|
+
*/
|
|
162
|
+
recordId?: string | null;
|
|
163
|
+
|
|
164
|
+
/** Callback for upload progress of each file */
|
|
165
|
+
onProgress?: (progress: FileUploadProgress) => void;
|
|
166
|
+
|
|
167
|
+
/** Optional abort signal to cancel all uploads */
|
|
168
|
+
signal?: AbortSignal;
|
|
169
|
+
}
|
|
204
170
|
|
|
205
|
-
|
|
171
|
+
interface FileUploadProgress {
|
|
172
|
+
fileName: string;
|
|
173
|
+
status: "pending" | "uploading" | "processing" | "success" | "error";
|
|
174
|
+
progress: number; // 0-100 for uploading, 0 for other states
|
|
175
|
+
error?: string;
|
|
206
176
|
}
|
|
207
177
|
```
|
|
208
178
|
|
|
209
|
-
|
|
179
|
+
**Returns:** `Promise<FileUploadResult[]>`
|
|
210
180
|
|
|
211
|
-
|
|
181
|
+
```typescript
|
|
182
|
+
interface FileUploadResult {
|
|
183
|
+
fileName: string;
|
|
184
|
+
size: number;
|
|
185
|
+
contentBodyId: string; // Always available
|
|
186
|
+
contentVersionId?: string; // Only if recordId was provided
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### createContentVersion(file, contentBodyId, recordId)
|
|
191
|
+
|
|
192
|
+
Manually create a ContentVersion record from a previously uploaded file.
|
|
193
|
+
|
|
194
|
+
**Parameters:**
|
|
195
|
+
|
|
196
|
+
- `file` (File): The file object (used for metadata like name)
|
|
197
|
+
- `contentBodyId` (string): The ContentBody ID from a previous upload
|
|
198
|
+
- `recordId` (string): The record ID for FirstPublishLocationId
|
|
199
|
+
|
|
200
|
+
**Returns:** `Promise<string | undefined>` - The ContentVersion ID if successful
|
|
212
201
|
|
|
213
202
|
```tsx
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
type UseFileUploadOptions,
|
|
217
|
-
} from "@salesforce/webapp-template-feature-react-file-upload-experimental";
|
|
203
|
+
const contentVersionId = await createContentVersion(file, "069xx000000xxxx", "001xx000000yyyy");
|
|
204
|
+
```
|
|
218
205
|
|
|
219
|
-
|
|
220
|
-
const { getInputProps, openFilePicker } = useFileUpload({
|
|
221
|
-
multiple: true,
|
|
222
|
-
maxFileSize: 50, // 50 MB limit
|
|
223
|
-
onUploadComplete: (files) => console.log("Uploaded:", files),
|
|
224
|
-
});
|
|
206
|
+
### getCurrentUserId()
|
|
225
207
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
</>
|
|
233
|
-
);
|
|
234
|
-
}
|
|
208
|
+
Get the current user's Salesforce ID.
|
|
209
|
+
|
|
210
|
+
**Returns:** `Promise<string>` - The current user ID
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
const userId = await getCurrentUserId();
|
|
235
214
|
```
|
|
236
215
|
|
|
237
|
-
|
|
216
|
+
## Building Your Own UI
|
|
238
217
|
|
|
239
|
-
|
|
218
|
+
This package provides the backend APIs - you can build any UI you want. Here's a basic example:
|
|
240
219
|
|
|
241
220
|
```tsx
|
|
242
|
-
import {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
recordId: accountId,
|
|
248
|
-
maxFileSize: 100, // 100 MB limit
|
|
249
|
-
onUploadComplete: (files) => console.log("Uploaded files:", files),
|
|
250
|
-
});
|
|
221
|
+
import {
|
|
222
|
+
upload,
|
|
223
|
+
type FileUploadProgress,
|
|
224
|
+
} from "@salesforce/webapp-template-feature-react-file-upload-experimental";
|
|
225
|
+
import { useState } from "react";
|
|
251
226
|
|
|
252
|
-
|
|
253
|
-
const
|
|
227
|
+
function CustomFileUpload({ recordId }: { recordId?: string }) {
|
|
228
|
+
const [progress, setProgress] = useState<Map<string, FileUploadProgress>>(new Map());
|
|
229
|
+
|
|
230
|
+
const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
231
|
+
const files = Array.from(event.target.files || []);
|
|
232
|
+
|
|
233
|
+
await upload({
|
|
234
|
+
files,
|
|
235
|
+
recordId,
|
|
236
|
+
onProgress: (fileProgress) => {
|
|
237
|
+
setProgress((prev) => new Map(prev).set(fileProgress.fileName, fileProgress));
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
};
|
|
254
241
|
|
|
255
242
|
return (
|
|
256
243
|
<div>
|
|
257
|
-
<
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
{item.file.name}: {item.state} {item.progress}%
|
|
244
|
+
<input type="file" multiple onChange={handleFileSelect} />
|
|
245
|
+
|
|
246
|
+
{Array.from(progress.entries()).map(([fileName, fileProgress]) => (
|
|
247
|
+
<div key={fileName}>
|
|
248
|
+
{fileName}: {fileProgress.status} - {fileProgress.progress}%
|
|
249
|
+
{fileProgress.error && <span>Error: {fileProgress.error}</span>}
|
|
264
250
|
</div>
|
|
265
251
|
))}
|
|
266
252
|
</div>
|
|
@@ -268,38 +254,78 @@ function MyUpload() {
|
|
|
268
254
|
}
|
|
269
255
|
```
|
|
270
256
|
|
|
271
|
-
##
|
|
257
|
+
## Agent/Vibe Integration Guide
|
|
272
258
|
|
|
273
|
-
|
|
259
|
+
When building file upload functionality with an AI agent like Vibe, follow this pattern:
|
|
274
260
|
|
|
275
|
-
|
|
261
|
+
### 1. Ask User for Record Context
|
|
276
262
|
|
|
277
|
-
|
|
263
|
+
```
|
|
264
|
+
Agent: "Do you want to link uploaded files to a specific record, or upload them first and link later?"
|
|
278
265
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
266
|
+
Options:
|
|
267
|
+
A) Link to existing record (provide recordId)
|
|
268
|
+
B) Upload only (link later with createContentVersion)
|
|
269
|
+
C) Link to current user
|
|
270
|
+
```
|
|
282
271
|
|
|
283
|
-
|
|
272
|
+
### 2. Implement Based on Response
|
|
273
|
+
|
|
274
|
+
**Option A: Link to existing record**
|
|
284
275
|
|
|
285
276
|
```tsx
|
|
286
|
-
|
|
277
|
+
await upload({ files, recordId: "001xx000000yyyy" });
|
|
287
278
|
```
|
|
288
279
|
|
|
289
|
-
|
|
280
|
+
**Option B: Upload only, link later**
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
const results = await upload({ files }); // No recordId
|
|
284
|
+
// Later: await createContentVersion(file, results[0].contentBodyId, newRecordId);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Option C: Link to current user**
|
|
290
288
|
|
|
291
|
-
|
|
289
|
+
```tsx
|
|
290
|
+
const userId = await getCurrentUserId();
|
|
291
|
+
await upload({ files, recordId: userId });
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### 3. Track Progress in Your UI
|
|
295
|
+
|
|
296
|
+
Always provide progress feedback to users:
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
onProgress: (progress) => {
|
|
300
|
+
// Update your UI based on:
|
|
301
|
+
// - progress.fileName
|
|
302
|
+
// - progress.status (pending, uploading, processing, success, error)
|
|
303
|
+
// - progress.progress (0-100)
|
|
304
|
+
// - progress.error (if status is 'error')
|
|
305
|
+
};
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Reference Implementation
|
|
309
|
+
|
|
310
|
+
This package includes a reference implementation with UI components for demonstration purposes. The components are located in `src/features/fileupload/` and include:
|
|
311
|
+
|
|
312
|
+
- FileUpload component with drop zone and progress dialog
|
|
313
|
+
- useFileUpload hook for custom implementations
|
|
314
|
+
- Test pages showing various usage patterns
|
|
315
|
+
|
|
316
|
+
These are **not exported** from the package but can be viewed as examples of how to build your own UI using the APIs.
|
|
317
|
+
|
|
318
|
+
## Dependencies
|
|
292
319
|
|
|
293
|
-
- **base-react-app** (built-in) – UI components (Button, Dialog, etc.) via shadcn are included in the base React app.
|
|
294
320
|
- **@salesforce/webapp-experimental** – For API client and Salesforce integration
|
|
321
|
+
- **@salesforce/sdk-data** – For data SDK integration
|
|
295
322
|
|
|
296
|
-
##
|
|
323
|
+
## Testing
|
|
297
324
|
|
|
298
|
-
|
|
325
|
+
Test the reference implementation locally:
|
|
299
326
|
|
|
300
327
|
```bash
|
|
301
|
-
npx nx
|
|
302
|
-
npx nx start
|
|
328
|
+
npx nx dev @salesforce/webapp-template-feature-react-file-upload-experimental
|
|
303
329
|
```
|
|
304
330
|
|
|
305
|
-
Open the app and navigate to the
|
|
331
|
+
Open the app and navigate to the test pages to see the APIs in action.
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.90.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.90.2...v1.90.3) (2026-03-11)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [1.90.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.90.1...v1.90.2) (2026-03-11)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
## [1.90.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.90.0...v1.90.1) (2026-03-11)
|
|
7
23
|
|
|
8
24
|
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@salesforce/sdk-data": "^1.90.
|
|
19
|
-
"@salesforce/webapp-experimental": "^1.90.
|
|
18
|
+
"@salesforce/sdk-data": "^1.90.3",
|
|
19
|
+
"@salesforce/webapp-experimental": "^1.90.3",
|
|
20
20
|
"@tailwindcss/vite": "^4.1.17",
|
|
21
21
|
"@tanstack/react-form": "^1.28.4",
|
|
22
22
|
"class-variance-authority": "^0.7.1",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
|
41
41
|
"@graphql-tools/utils": "^11.0.0",
|
|
42
42
|
"@playwright/test": "^1.49.0",
|
|
43
|
-
"@salesforce/vite-plugin-webapp-experimental": "^1.90.
|
|
43
|
+
"@salesforce/vite-plugin-webapp-experimental": "^1.90.3",
|
|
44
44
|
"@testing-library/jest-dom": "^6.6.3",
|
|
45
45
|
"@testing-library/react": "^16.1.0",
|
|
46
46
|
"@testing-library/user-event": "^14.5.2",
|