Upload Components
The Upload Components provide comprehensive file upload functionality for the WorkPayCore Frontend application. The system includes multiple upload component variants designed for different use cases, from simple file uploads to complex multi-file management with previews and validation.
Overview
This document covers all upload-related components that handle file selection, drag-and-drop functionality, file previews, and upload validation within the WorkPayCore Frontend application. All upload components are built using react-dropzone for consistent drag-and-drop behavior.
Components Overview
Core Upload Components
- WPFileUploaderV1 - Legacy file uploader component
- WPFileUploaderV2 - Enhanced file uploader with modern UI
- FileUploadV2 - Advanced upload with document count control
- StableFormFileUpload - Form-integrated upload with validation
Specialized Upload Components
- WPImageUploader - Image-specific upload with preview
- WPProfilePicUpload - Profile picture upload component
- WPControlledFileUpload - Controlled upload component
- GlobalFileUpload - Global utility upload component
Legacy Upload Components
- FileUpload - Original themed file upload
- FileUploadLeaves - Leave-specific file upload
- FileUploadLeavesV2 - Enhanced leave file upload
WPFileUploaderV1
Legacy file uploader component with basic drag-and-drop functionality.
Component Location
import WPFileUploaderV1 from 'components/Upload/WPFileUploaderV1';
Features
Basic Upload Functionality
- Drag and drop file selection
- Browse files button
- Single file upload
- File name display
- Remove file capability
Form Integration
- React Hook Form integration
- Form validation support
- Error display
Usage Examples
Basic V1 Upload
import WPFileUploaderV1 from 'components/Upload/WPFileUploaderV1';
function BasicUploadV1() {
return (
<Box p={4}>
<WPFileUploaderV1 />
</Box>
);
}
Form Integrated V1 Upload
import WPFileUploaderV1 from 'components/Upload/WPFileUploaderV1';
import { useForm } from 'react-hook-form';
function FormUploadV1() {
const { handleSubmit, watch } = useForm();
const uploadedFile = watch('file');
const onSubmit = data => {
console.log('Uploaded file:', data.file);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<WPFileUploaderV1 />
<Button type='submit' disabled={!uploadedFile}>
Submit
</Button>
</form>
);
}
WPFileUploaderV2
Enhanced file uploader with modern UI, multiple file support, and advanced features.
Component Location
import WPFileUploaderV2 from 'components/Upload/WPFileUploaderV2';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | - | 'file-upload-v2' | Input name attribute |
| multiple | boolean | - | false | Allow multiple file selection |
| maxSizeInBytes | number | - | 200MB | Maximum file size |
| fileTypes | array | - | ['.gif', '.jpg', '.jpeg', '.png', '.pdf'] | Accepted file types |
| defaultFiles | File[] | - | null | Pre-loaded files |
| handleSelectedFile | function | ✓ | - | File selection handler |
| handleRemoveFile | function | ✓ | - | File removal handler |
| isInvalid | boolean | - | false | Validation error state |
| showFileTypes | boolean | - | true | Show accepted file types |
TypeScript Interface
interface WPFileUploaderV2Props {
name?: string;
multiple?: boolean;
maxSizeInBytes?: number;
fileTypes?: string[];
defaultFiles?: File[] | null;
handleSelectedFile: (files: File | File[]) => void;
handleRemoveFile: () => void;
isInvalid?: boolean;
showFileTypes: boolean;
}
Enhanced Features
Multi-File Support
- Multiple file selection
- File list management
- Individual file removal
- File preview with metadata
Modern UI/UX
- Drag-and-drop zone with visual feedback
- File preview cards with thumbnails
- Open/preview functionality
- Responsive design
File Management
- File size validation
- File type restrictions
- File replacement
- Batch file operations
Usage Examples
Basic V2 Upload
import WPFileUploaderV2 from 'components/Upload/WPFileUploaderV2';
function BasicUploadV2() {
const [files, setFiles] = useState([]);
const handleFileSelect = selectedFiles => {
setFiles(Array.isArray(selectedFiles) ? selectedFiles : [selectedFiles]);
};
const handleFileRemove = () => {
setFiles([]);
};
return (
<WPFileUploaderV2
name='document-upload'
multiple={true}
maxSizeInBytes={50 * 1024 * 1024} // 50MB
fileTypes={['.pdf', '.doc', '.docx']}
handleSelectedFile={handleFileSelect}
handleRemoveFile={handleFileRemove}
showFileTypes={true}
/>
);
}
Multiple File Upload with Validation
import WPFileUploaderV2 from 'components/Upload/WPFileUploaderV2';
function ValidatedMultiUpload() {
const [files, setFiles] = useState([]);
const [errors, setErrors] = useState([]);
const validateFiles = selectedFiles => {
const newErrors = [];
selectedFiles.forEach((file, index) => {
if (file.size > 10 * 1024 * 1024) {
// 10MB limit
newErrors.push(`File ${index + 1} exceeds size limit`);
}
if (!['image/jpeg', 'image/png', 'application/pdf'].includes(file.type)) {
newErrors.push(`File ${index + 1} has invalid type`);
}
});
setErrors(newErrors);
return newErrors.length === 0;
};
const handleFileSelect = selectedFiles => {
const fileArray = Array.isArray(selectedFiles)
? selectedFiles
: [selectedFiles];
if (validateFiles(fileArray)) {
setFiles(fileArray);
}
};
return (
<VStack spacing={4}>
<WPFileUploaderV2
name='validated-upload'
multiple={true}
maxSizeInBytes={10 * 1024 * 1024}
fileTypes={['.jpg', '.jpeg', '.png', '.pdf']}
handleSelectedFile={handleFileSelect}
handleRemoveFile={() => setFiles([])}
isInvalid={errors.length > 0}
showFileTypes={true}
/>
{errors.length > 0 && (
<Alert status='error'>
<AlertIcon />
<VStack align='start'>
{errors.map((error, index) => (
<Text key={index}>{error}</Text>
))}
</VStack>
</Alert>
)}
</VStack>
);
}
Upload with Default Files
import WPFileUploaderV2 from 'components/Upload/WPFileUploaderV2';
function UploadWithDefaults() {
const [files, setFiles] = useState([]);
// Load existing files from API
useEffect(() => {
const loadExistingFiles = async () => {
const existingFiles = await fetchUserDocuments();
setFiles(existingFiles);
};
loadExistingFiles();
}, []);
return (
<WPFileUploaderV2
name='existing-files-upload'
multiple={true}
defaultFiles={files}
handleSelectedFile={newFiles => {
const fileArray = Array.isArray(newFiles) ? newFiles : [newFiles];
setFiles([...files, ...fileArray]);
}}
handleRemoveFile={() => {
// Custom removal logic
setFiles(files.slice(0, -1));
}}
/>
);
}
FileUploadV2
Advanced upload component with document count control and enhanced file management.
Component Location
import FileUploadV2 from 'components/Upload/FileUploadV2';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | - | 'file-upload-v2' | Input name |
| multiple | boolean | - | false | Multiple file support |
| maxSizeInBytes | number | - | 200MB | Max file size |
| fileTypes | array | - | ['.gif', '.jpg', '.jpeg', '.png', '.pdf'] | Accepted types |
| defaultFiles | File[] | - | null | Default files |
| handleSelectedFile | function | ✓ | - | File selection handler |
| handleRemoveFile | function | - | - | File removal handler |
| isInvalid | boolean | - | false | Error state |
| showFileTypes | boolean | - | true | Show file types |
| showOpenButton | boolean | - | true | Show open/preview button |
| requiredDocumentsCount | number | - | 1 | Required document count |
Enhanced Features
Document Count Management
- Required document count validation
- Auto-hide upload area when limit reached
- Document count-based UI behavior
Advanced File Operations
- File preview/open functionality
- Individual file removal
- File metadata display
Smart UI Behavior
- Conditional upload area visibility
- Dynamic file type display
- Enhanced user feedback
Usage Examples
Document Upload with Count Limit
import FileUploadV2 from 'components/Upload/FileUploadV2';
function DocumentUploadWithLimit() {
const [documents, setDocuments] = useState([]);
const requiredDocs = 3;
const handleFileSelect = files => {
const fileArray = Array.isArray(files) ? files : [files];
setDocuments(prev => [...prev, ...fileArray]);
};
const handleFileRemove = () => {
setDocuments(prev => prev.slice(0, -1));
};
return (
<VStack spacing={4}>
<Text fontWeight='bold'>
Upload Required Documents ({documents.length}/{requiredDocs})
</Text>
<FileUploadV2
name='required-documents'
multiple={true}
fileTypes={['.pdf', '.doc', '.docx']}
maxSizeInBytes={20 * 1024 * 1024} // 20MB
requiredDocumentsCount={requiredDocs}
handleSelectedFile={handleFileSelect}
handleRemoveFile={handleFileRemove}
showOpenButton={true}
showFileTypes={true}
/>
<Progress
value={(documents.length / requiredDocs) * 100}
colorScheme='green'
width='100%'
/>
</VStack>
);
}
Leave Application Upload
import FileUploadV2 from 'components/Upload/FileUploadV2';
function LeaveApplicationUpload() {
const [supportingDocs, setSupportingDocs] = useState([]);
return (
<FormControl>
<FormLabel>Supporting Documents</FormLabel>
<FormHelperText mb={4}>
Upload medical certificates or other supporting documents
</FormHelperText>
<FileUploadV2
name='leave-supporting-docs'
multiple={true}
fileTypes={['.pdf', '.jpg', '.jpeg', '.png']}
maxSizeInBytes={10 * 1024 * 1024}
requiredDocumentsCount={2}
handleSelectedFile={files => {
const fileArray = Array.isArray(files) ? files : [files];
setSupportingDocs(prev => [...prev, ...fileArray]);
}}
handleRemoveFile={() => {
setSupportingDocs(prev => prev.slice(0, -1));
}}
showFileTypes={true}
showOpenButton={true}
/>
</FormControl>
);
}
StableFormFileUpload
Form-integrated upload component with comprehensive validation and error handling.
Component Location
import { StableFormFileUpload } from 'components/Upload/StableFormFileUpload';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | ✓ | - | Form field name |
| label | string | ✓ | - | Field label |
| isRequired | boolean | - | false | Required field |
| isDisabled | boolean | - | false | Disabled state |
| isInvalid | boolean | - | false | Invalid state |
| isReadOnly | boolean | - | false | Read-only state |
| acceptProps | object | - | - | Accept configuration |
| multiple | boolean | - | false | Multiple files |
| value | File[] | - | [] | Current files |
| error | string | - | - | Error message |
| onChange | function | - | - | Change handler |
| onRemove | function | - | - | Remove handler |
| maxSize | number | - | 10MB | Max file size |
| maxFiles | number | - | 5 | Max file count |
TypeScript Interface
interface StableFormFileUploadProps {
name: string;
label: string;
isRequired?: boolean;
isDisabled?: boolean;
isInvalid?: boolean;
isReadOnly?: boolean;
acceptProps?: {
mimeTypes: readonly string[];
displayText?: string;
};
multiple?: boolean;
value?: File[];
error?: string;
onChange?: (files: File[]) => void;
onRemove?: (file: File) => void;
maxSize?: number;
maxFiles?: number;
gridGap?: number;
justify?: string;
padding?: string;
minHeight?: number;
flexDir?: 'row' | 'column';
}
Form Integration Features
Chakra UI Form Integration
- FormControl integration
- FormLabel and validation
- FormErrorMessage display
- Accessibility support
Advanced Validation
- File size validation
- File type validation
- File count limits
- Custom error messages
File Preview System
- File preview cards
- File metadata display
- Individual file removal
- File type icons
Usage Examples
Basic Form Upload
import { StableFormFileUpload } from 'components/Upload/StableFormFileUpload';
function FormWithUpload() {
const [files, setFiles] = useState([]);
const [error, setError] = useState('');
const handleFileChange = selectedFiles => {
setFiles(selectedFiles);
setError(''); // Clear errors on successful selection
};
const handleFileRemove = fileToRemove => {
setFiles(files.filter(file => file !== fileToRemove));
};
return (
<VStack spacing={6}>
<StableFormFileUpload
name='documents'
label='Upload Documents'
isRequired={true}
multiple={true}
value={files}
error={error}
onChange={handleFileChange}
onRemove={handleFileRemove}
acceptProps={{
mimeTypes: ['application/pdf', 'image/jpeg', 'image/png'],
displayText: 'Accepts PDF, JPEG, and PNG files (10MB max)',
}}
maxSize={10 * 1024 * 1024} // 10MB
maxFiles={3}
/>
</VStack>
);
}
Employee Onboarding Upload
import { StableFormFileUpload } from 'components/Upload/StableFormFileUpload';
import { useForm, Controller } from 'react-hook-form';
function EmployeeOnboardingForm() {
const {
control,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = data => {
console.log('Form data:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<VStack spacing={6}>
<Controller
name='resume'
control={control}
rules={{ required: 'Resume is required' }}
render={({ field, fieldState }) => (
<StableFormFileUpload
name='resume'
label='Resume/CV'
isRequired={true}
value={field.value || []}
error={fieldState.error?.message}
onChange={field.onChange}
onRemove={file => {
const newFiles = field.value?.filter(f => f !== file) || [];
field.onChange(newFiles);
}}
acceptProps={{
mimeTypes: ['application/pdf', 'application/msword'],
displayText: 'PDF or Word documents only',
}}
maxSize={5 * 1024 * 1024} // 5MB
maxFiles={1}
/>
)}
/>
<Controller
name='certificates'
control={control}
render={({ field, fieldState }) => (
<StableFormFileUpload
name='certificates'
label='Certificates (Optional)'
multiple={true}
value={field.value || []}
error={fieldState.error?.message}
onChange={field.onChange}
onRemove={file => {
const newFiles = field.value?.filter(f => f !== file) || [];
field.onChange(newFiles);
}}
acceptProps={{
mimeTypes: ['application/pdf', 'image/jpeg', 'image/png'],
displayText: 'PDF or image files',
}}
maxFiles={5}
/>
)}
/>
<Button type='submit' colorScheme='blue'>
Submit Application
</Button>
</VStack>
</form>
);
}
Validation with Custom Error Handling
import { StableFormFileUpload } from 'components/Upload/StableFormFileUpload';
function ValidatedUpload() {
const [files, setFiles] = useState([]);
const [error, setError] = useState('');
const validateFiles = selectedFiles => {
// Custom validation logic
const totalSize = selectedFiles.reduce((sum, file) => sum + file.size, 0);
if (totalSize > 50 * 1024 * 1024) {
// 50MB total
return 'Total file size exceeds 50MB limit';
}
const invalidFiles = selectedFiles.filter(
file =>
!file.type.startsWith('image/') && file.type !== 'application/pdf',
);
if (invalidFiles.length > 0) {
return 'Only images and PDF files are allowed';
}
return null;
};
const handleFileChange = selectedFiles => {
const validationError = validateFiles(selectedFiles);
if (validationError) {
setError(validationError);
} else {
setFiles(selectedFiles);
setError('');
}
};
return (
<StableFormFileUpload
name='validated-files'
label='Upload Files with Validation'
multiple={true}
value={files}
error={error}
onChange={handleFileChange}
onRemove={file => {
const newFiles = files.filter(f => f !== file);
setFiles(newFiles);
setError(''); // Clear error when removing files
}}
acceptProps={{
mimeTypes: ['image/*', 'application/pdf'],
displayText: 'Images and PDF files only',
}}
maxFiles={10}
/>
);
}
WPImageUploader
Specialized image upload component with preview functionality and React Hook Form integration.
Component Location
import WPImageUploader from 'components/Upload/WPImageUploader';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | ✓ | - | Form field name |
| isDisabled | boolean | - | false | Disabled state |
| preview | string | - | - | Preview image URL |
| removeImage | function | - | - | Remove image handler |
| replaceImg | function | - | - | Replace image handler |
| accept | object | - | - | Accepted file types |
Features
Image-Specific Functionality
- Image preview with thumbnails
- Image replacement workflow
- Drag-and-drop image selection
- Image file validation
Form Integration
- React Hook Form integration
- Automatic form registration
- Form validation support
Usage Examples
Basic Image Upload
import WPImageUploader from 'components/Upload/WPImageUploader';
import { useForm, FormProvider } from 'react-hook-form';
function ImageUploadForm() {
const methods = useForm();
const [previewUrl, setPreviewUrl] = useState('');
const handleImageRemove = () => {
setPreviewUrl('');
methods.setValue('profileImage', null);
};
const handleImageReplace = () => {
setPreviewUrl('');
methods.setValue('profileImage', null);
};
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<WPImageUploader
name='profileImage'
preview={previewUrl}
removeImage={handleImageRemove}
replaceImg={handleImageReplace}
accept={{
'image/*': ['.png', '.jpg', '.jpeg', '.gif'],
}}
/>
<Button type='submit'>Save</Button>
</form>
</FormProvider>
);
}
Profile Picture Upload
import WPImageUploader from 'components/Upload/WPImageUploader';
function ProfilePictureUpload() {
const [currentImage, setCurrentImage] = useState(user.profilePicture);
return (
<VStack spacing={4}>
<Avatar size='xl' src={currentImage} name={user.name} />
<FormProvider {...methods}>
<WPImageUploader
name='profilePicture'
preview={currentImage}
removeImage={() => {
setCurrentImage('');
// API call to remove profile picture
}}
replaceImg={() => {
setCurrentImage('');
}}
accept={{
'image/*': ['.png', '.jpg', '.jpeg'],
}}
/>
</FormProvider>
</VStack>
);
}
WPProfilePicUpload
Minimal profile picture upload component for simple use cases.
Component Location
import WPProfilePicUpload from 'components/Upload/WPProfilePicUpload';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| handleSelectedFile | function | ✓ | - | File selection handler |
| name | string | ✓ | - | Input name |
| label | string | ✓ | - | Upload label/button |
Usage Examples
import WPProfilePicUpload from 'components/Upload/WPProfilePicUpload';
function SimpleProfileUpload() {
const handleFileSelect = files => {
const file = files[0];
if (file) {
// Process uploaded file
uploadProfilePicture(file);
}
};
return (
<WPProfilePicUpload
name='profile-pic'
label={<Button>Change Profile Picture</Button>}
handleSelectedFile={handleFileSelect}
/>
);
}
WPControlledFileUpload
Controlled upload component for use with external state management.
Component Location
import WPControlledFileUpload from 'components/Upload/WPControledFileUpload';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| value | object | ✓ | - | Current file value |
| onChange | function | ✓ | - | Value change handler |
| height | string | - | - | Component height |
| type | string | - | - | Theme type |
Usage Examples
import WPControlledFileUpload from 'components/Upload/WPControledFileUpload';
function ControlledUpload() {
const [selectedFile, setSelectedFile] = useState(null);
return (
<WPControlledFileUpload
value={selectedFile}
onChange={setSelectedFile}
type='fileB'
height='200px'
/>
);
}
GlobalFileUpload
Global utility upload component for general file upload needs.
Component Location
import GlobalFileUpload from 'components/Upload/GlobalFileUpload';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| setDefaultFileName | string | - | - | Default file name |
| getFile | function | ✓ | - | File handler |
Usage Examples
import GlobalFileUpload from 'components/Upload/GlobalFileUpload';
function GeneralUpload() {
const handleFileUpload = files => {
const file = files[0];
// Process file
console.log('Uploaded:', file.name);
};
return (
<GlobalFileUpload
getFile={handleFileUpload}
setDefaultFileName='document.pdf'
/>
);
}
Upload Component Patterns
File Validation Patterns
// Common file validation utilities
const validateFileSize = (file, maxSizeInBytes) => {
return file.size <= maxSizeInBytes;
};
const validateFileType = (file, allowedTypes) => {
return allowedTypes.some(
type => file.type.includes(type) || file.name.endsWith(type),
);
};
const validateFiles = (files, options = {}) => {
const {
maxSize = 10 * 1024 * 1024, // 10MB
allowedTypes = ['image/*', 'application/pdf'],
maxFiles = 5,
} = options;
const errors = [];
if (files.length > maxFiles) {
errors.push(`Maximum ${maxFiles} files allowed`);
}
files.forEach((file, index) => {
if (!validateFileSize(file, maxSize)) {
errors.push(`File ${index + 1} exceeds size limit`);
}
if (!validateFileType(file, allowedTypes)) {
errors.push(`File ${index + 1} has invalid type`);
}
});
return errors;
};
Upload Progress Pattern
import { useState } from 'react';
function UploadWithProgress() {
const [uploadProgress, setUploadProgress] = useState(0);
const [isUploading, setIsUploading] = useState(false);
const uploadFile = async file => {
setIsUploading(true);
setUploadProgress(0);
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
// Track upload progress
onUploadProgress: progressEvent => {
const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
setUploadProgress(progress);
},
});
if (response.ok) {
console.log('Upload successful');
}
} catch (error) {
console.error('Upload failed:', error);
} finally {
setIsUploading(false);
setUploadProgress(0);
}
};
return (
<VStack spacing={4}>
<WPFileUploaderV2
handleSelectedFile={files => {
const file = Array.isArray(files) ? files[0] : files;
uploadFile(file);
}}
handleRemoveFile={() => {}}
/>
{isUploading && (
<Box width='100%'>
<Text mb={2}>Uploading... {uploadProgress}%</Text>
<Progress value={uploadProgress} colorScheme='green' />
</Box>
)}
</VStack>
);
}
Multiple Upload Management
function MultipleUploadManager() {
const [files, setFiles] = useState([]);
const [uploadedFiles, setUploadedFiles] = useState([]);
const handleFileSelect = newFiles => {
const fileArray = Array.isArray(newFiles) ? newFiles : [newFiles];
setFiles(prev => [...prev, ...fileArray]);
};
const handleFileRemove = fileToRemove => {
setFiles(files.filter(file => file !== fileToRemove));
};
const uploadAllFiles = async () => {
const uploadPromises = files.map(async file => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
return response.json();
});
try {
const results = await Promise.all(uploadPromises);
setUploadedFiles(results);
setFiles([]); // Clear pending files
} catch (error) {
console.error('Batch upload failed:', error);
}
};
return (
<VStack spacing={4}>
<WPFileUploaderV2
multiple={true}
handleSelectedFile={handleFileSelect}
handleRemoveFile={handleFileRemove}
/>
{files.length > 0 && (
<Button onClick={uploadAllFiles} colorScheme='blue'>
Upload All Files ({files.length})
</Button>
)}
{uploadedFiles.length > 0 && (
<Box>
<Text fontWeight='bold'>Uploaded Files:</Text>
{uploadedFiles.map((file, index) => (
<Text key={index}>{file.filename}</Text>
))}
</Box>
)}
</VStack>
);
}
File Type Configuration
Common File Type Patterns
// Image files
const imageTypes = {
mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
extensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
displayText: 'JPEG, PNG, GIF, or WebP images',
};
// Document files
const documentTypes = {
mimeTypes: [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
extensions: ['.pdf', '.doc', '.docx'],
displayText: 'PDF or Word documents',
};
// Spreadsheet files
const spreadsheetTypes = {
mimeTypes: [
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/csv',
],
extensions: ['.xls', '.xlsx', '.csv'],
displayText: 'Excel or CSV files',
};
// All office files
const officeTypes = {
mimeTypes: [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
],
extensions: ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'],
displayText: 'Office documents and PDFs',
};
File Type Usage Examples
// Employee document upload
function EmployeeDocumentUpload() {
return (
<StableFormFileUpload
name='employee-documents'
label='Employee Documents'
acceptProps={documentTypes}
multiple={true}
maxFiles={5}
/>
);
}
// Profile picture upload
function ProfileImageUpload() {
return (
<WPImageUploader
name='profile-image'
accept={{
'image/*': imageTypes.extensions,
}}
/>
);
}
// Payroll data upload
function PayrollDataUpload() {
return (
<FileUploadV2
name='payroll-data'
fileTypes={spreadsheetTypes.extensions}
maxSizeInBytes={25 * 1024 * 1024} // 25MB
showFileTypes={true}
/>
);
}
Styling and Theming
Upload Component Styling
// Custom styled upload
function StyledUpload() {
return (
<Box
border='2px dashed'
borderColor='blue.300'
borderRadius='lg'
p={6}
bg='blue.50'
_hover={{
borderColor: 'blue.500',
bg: 'blue.100',
}}
>
<WPFileUploaderV2
handleSelectedFile={handleFileSelect}
handleRemoveFile={handleFileRemove}
/>
</Box>
);
}
Responsive Upload Areas
function ResponsiveUpload() {
return (
<Box
width={{ base: '100%', md: '500px' }}
height={{ base: '150px', md: '200px' }}
>
<WPFileUploaderV2
handleSelectedFile={handleFileSelect}
handleRemoveFile={handleFileRemove}
/>
</Box>
);
}
Accessibility
Screen Reader Support
function AccessibleUpload() {
return (
<FormControl>
<FormLabel htmlFor='accessible-upload'>
Upload Required Documents
</FormLabel>
<FormHelperText>
Upload PDF or image files. Maximum 10MB per file.
</FormHelperText>
<StableFormFileUpload
name='accessible-upload'
label='Required Documents'
acceptProps={{
mimeTypes: ['application/pdf', 'image/*'],
displayText: 'PDF or image files (10MB max)',
}}
multiple={true}
/>
</FormControl>
);
}
Keyboard Navigation
function KeyboardAccessibleUpload() {
const [isFocused, setIsFocused] = useState(false);
return (
<Box
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') {
// Trigger file picker
document.getElementById('file-input').click();
}
}}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
outline={isFocused ? '2px solid' : 'none'}
outlineColor='blue.500'
tabIndex={0}
role='button'
aria-label='Upload files'
>
<WPFileUploaderV2
name='keyboard-upload'
handleSelectedFile={handleFileSelect}
handleRemoveFile={handleFileRemove}
/>
</Box>
);
}
Testing Strategies
Upload Component Testing
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import WPFileUploaderV2 from 'components/Upload/WPFileUploaderV2';
describe('WPFileUploaderV2', () => {
const mockHandleSelectedFile = jest.fn();
const mockHandleRemoveFile = jest.fn();
beforeEach(() => {
mockHandleSelectedFile.mockClear();
mockHandleRemoveFile.mockClear();
});
it('renders upload area', () => {
render(
<WPFileUploaderV2
handleSelectedFile={mockHandleSelectedFile}
handleRemoveFile={mockHandleRemoveFile}
/>,
);
expect(screen.getByText(/drag and drop files here/i)).toBeInTheDocument();
expect(screen.getByText(/click to upload/i)).toBeInTheDocument();
});
it('handles file selection', async () => {
const user = userEvent.setup();
render(
<WPFileUploaderV2
handleSelectedFile={mockHandleSelectedFile}
handleRemoveFile={mockHandleRemoveFile}
/>,
);
const file = new File(['test content'], 'test.pdf', {
type: 'application/pdf',
});
const input = screen.getByRole('button');
await user.upload(input, file);
await waitFor(() => {
expect(mockHandleSelectedFile).toHaveBeenCalledWith([file]);
});
});
it('shows file preview after upload', async () => {
const file = new File(['test'], 'test.pdf', { type: 'application/pdf' });
render(
<WPFileUploaderV2
defaultFiles={[file]}
handleSelectedFile={mockHandleSelectedFile}
handleRemoveFile={mockHandleRemoveFile}
/>,
);
expect(screen.getByText('test.pdf')).toBeInTheDocument();
expect(screen.getByText(/file size/i)).toBeInTheDocument();
});
it('handles file removal', async () => {
const user = userEvent.setup();
const file = new File(['test'], 'test.pdf', { type: 'application/pdf' });
render(
<WPFileUploaderV2
defaultFiles={[file]}
handleSelectedFile={mockHandleSelectedFile}
handleRemoveFile={mockHandleRemoveFile}
/>,
);
const removeButton = screen.getByRole('button', { name: /remove/i });
await user.click(removeButton);
expect(mockHandleRemoveFile).toHaveBeenCalled();
});
it('validates file types', () => {
render(
<WPFileUploaderV2
fileTypes={['.pdf']}
isInvalid={true}
handleSelectedFile={mockHandleSelectedFile}
handleRemoveFile={mockHandleRemoveFile}
/>,
);
const uploadArea = screen.getByRole('button');
expect(uploadArea).toHaveStyle('border-color: red');
});
});
Integration Testing
// Test complete upload workflow
describe('Upload Integration', () => {
it('uploads file and shows success', async () => {
const mockUploadAPI = jest.fn().mockResolvedValue({
success: true,
fileId: '123',
});
render(<UploadWithAPIIntegration uploadAPI={mockUploadAPI} />);
const file = new File(['content'], 'test.pdf', {
type: 'application/pdf',
});
// Upload file
const input = screen.getByRole('button');
await userEvent.upload(input, file);
// Click upload button
const uploadButton = screen.getByText('Upload');
await userEvent.click(uploadButton);
// Wait for success message
await waitFor(() => {
expect(screen.getByText('Upload successful')).toBeInTheDocument();
});
expect(mockUploadAPI).toHaveBeenCalledWith(
expect.objectContaining({
file: expect.any(File),
}),
);
});
});
Performance Optimization
File Processing Optimization
// Optimize large file handling
function OptimizedUpload() {
const [isProcessing, setIsProcessing] = useState(false);
const processFiles = useCallback(async files => {
setIsProcessing(true);
try {
// Process files in chunks for better performance
const chunkSize = 3;
const chunks = [];
for (let i = 0; i < files.length; i += chunkSize) {
chunks.push(files.slice(i, i + chunkSize));
}
for (const chunk of chunks) {
await Promise.all(chunk.map(file => processIndividualFile(file)));
}
} finally {
setIsProcessing(false);
}
}, []);
return (
<Box>
<WPFileUploaderV2
multiple={true}
handleSelectedFile={processFiles}
handleRemoveFile={() => {}}
/>
{isProcessing && <Spinner />}
</Box>
);
}
Memory Management
// Clean up object URLs to prevent memory leaks
function MemoryOptimizedUpload() {
const [files, setFiles] = useState([]);
const [previews, setPreviews] = useState([]);
useEffect(() => {
// Create object URLs for previews
const newPreviews = files.map(file => ({
file,
url: URL.createObjectURL(file),
}));
setPreviews(newPreviews);
// Cleanup function
return () => {
newPreviews.forEach(preview => {
URL.revokeObjectURL(preview.url);
});
};
}, [files]);
return (
<VStack>
<WPFileUploaderV2
handleSelectedFile={setFiles}
handleRemoveFile={() => setFiles([])}
/>
{previews.map(({ file, url }) => (
<Image key={file.name} src={url} alt={file.name} />
))}
</VStack>
);
}
Best Practices
Upload Component Selection
- Use WPFileUploaderV2 for new implementations requiring modern UI
- Use StableFormFileUpload for form-integrated uploads with validation
- Use FileUploadV2 when document count control is needed
- Use WPImageUploader specifically for image uploads with preview
File Validation
- Always validate file size to prevent large uploads
- Restrict file types based on security requirements
- Implement client and server validation for robust security
- Provide clear error messages for validation failures
User Experience
- Show upload progress for large files
- Provide file previews when possible
- Enable drag-and-drop for better usability
- Clear error states and recovery options
Performance
- Process files in chunks for large batches
- Clean up object URLs to prevent memory leaks
- Implement file compression for images when appropriate
- Use progressive upload for very large files
This comprehensive upload system provides flexible, robust file upload capabilities for all use cases within the WorkPayCore Frontend application.