A React component that allows users to capture photos of receipts on both mobile and desktop devices. The component provides a complete camera interface with image compression, preview, and permission handling.
✅ Access device camera using HTML5 Media Capture API (navigator.mediaDevices.getUserMedia())
✅ Live camera preview with high-quality video stream
✅ Capture button with visual feedback and animations
✅ Front/back camera switching for mobile devices
✅ Image preview after capture with dimensions displayed
✅ Retake option to discard and recapture photos
✅ Automatic image compression using Canvas API
✅ Graceful permission handling with user-friendly error messages
✅ iOS Safari & Android Chrome fully supported
✅ File upload fallback for desktop users
✅ Accessibility features with ARIA labels and keyboard navigation
✅ Comprehensive test coverage
The component is already included in the project. No additional dependencies are required beyond what's in the project's package.json.
import { CameraCapture } from '@/components/CameraCapture';
function ReceiptUpload() {
const handleCapture = (file: File) => {
console.log('Captured image:', file);
// Upload file to server
uploadReceipt(file);
};
return (
<div>
<h2>Capture Receipt</h2>
<CameraCapture onCapture={handleCapture} />
</div>
);
}import { CameraCapture } from '@/components/CameraCapture';
function ReceiptUpload() {
const handleCapture = (file: File) => {
console.log('Image captured and compressed:', file.name, file.size);
};
const handleError = (error: Error) => {
console.error('Camera error:', error.message);
// Show user-friendly error message
toast.error('Unable to access camera');
};
return (
<CameraCapture
onCapture={handleCapture}
onError={handleError}
maxFileSize={5242880}
compressionQuality={0.8}
/>
);
}<CameraCapture
onCapture={handleCapture}
maxFileSize={3 * 1024 * 1024} // 3MB
compressionQuality={0.7} // Lower quality for smaller files
/>| Prop | Type | Default | Description |
|---|---|---|---|
onCapture |
(file: File) => void |
Required | Callback function called when image is successfully captured and compressed |
onError |
(error: Error) => void |
Optional | Callback function called when an error occurs |
maxFileSize |
number |
5242880 (5MB) |
Maximum file size in bytes after compression |
compressionQuality |
number |
0.8 |
JPEG quality for compression (0-1) |
Main component for capturing receipt photos.
interface CameraCaptureProps {
onCapture: (file: File) => void;
onError?: (error: Error) => void;
maxFileSize?: number; // in bytes
compressionQuality?: number; // 0-1
}Located in src/utils/imageCompression.ts:
Compress an image file using Canvas API.
import { compressImage } from '@/utils/imageCompression';
const compressed = await compressImage(file, {
maxWidth: 1920,
maxHeight: 1440,
quality: 0.8
});Convert a Blob to a File object.
import { blobToFile } from '@/utils/imageCompression';
const file = blobToFile(blob, 'receipt.jpg');Format bytes to human-readable file size.
formatFileSize(1024); // "1 KB"
formatFileSize(1024 * 1024); // "1 MB"Check if file is a supported image type (JPEG, PNG, WebP).
Located in src/utils/cameraPermissions.ts:
Request camera access from the user.
import { requestCameraPermission } from '@/utils/cameraPermissions';
const stream = await requestCameraPermission({
video: { facingMode: 'environment' },
audio: false
});Stop all tracks in a media stream.
import { stopCameraStream } from '@/utils/cameraPermissions';
stopCameraStream(mediaStream);Check the current camera permission status without requesting.
import { checkCameraPermission } from '@/utils/cameraPermissions';
const status = await checkCameraPermission(); // 'granted' | 'denied' | 'prompt'Convert technical error to user-friendly message.
✅ Supported
- Works with iOS 11+
- Requires HTTPS connection
- Camera access permission must be granted through Settings → Safari
- Both front and back cameras supported
- Use
navigator.mediaDevices.getUserMedia()for best compatibility
✅ Supported
- Works with Chrome 47+
- Requires HTTPS connection
- Camera permission must be granted through app settings
- Both front and back cameras supported
- Handles device orientation changes
✅ Chrome - Full support ✅ Firefox - Full support ✅ Safari - Full support (macOS 10.15+) ✅ Edge - Full support
Note: HTTPS is required on all platforms except localhost.
The component handles various permission and access errors gracefully:
"Camera permission denied. Please allow access in your browser settings."
"No camera device found. Please check if your device has a camera."
"Camera access requires a secure connection (HTTPS)"
"Camera API is not supported in this browser"
The component automatically compresses images to:
- Max dimensions: 1920x1440 (default)
- JPEG quality: 0.8 (default, 0-1 scale)
- Max file size: 5MB (default)
- Maintains aspect ratio
If the compressed image exceeds the max file size, the component automatically retries with lower quality (0.2 reduction).
- Load image from File using FileReader
- Create new Image object from data URL
- Calculate new dimensions maintaining aspect ratio
- Draw image to Canvas with new dimensions
- Convert canvas to JPEG blob with specified quality
- Convert blob to File object
The component includes several accessibility features:
- ARIA Labels: All buttons have descriptive
aria-labelattributes - Keyboard Navigation: All controls are keyboard accessible
- Focus Management: Visual focus indicators on all interactive elements
- Semantic HTML: Proper use of HTML5 elements and attributes
The component includes comprehensive test coverage with 30+ test cases.
npm test- ✅ Camera access and initialization
- ✅ Permission handling
- ✅ Image capture and preview
- ✅ Image compression
- ✅ Camera switching (front/back)
- ✅ Retake functionality
- ✅ File upload fallback
- ✅ Error scenarios
- ✅ Accessibility features
CameraCapture.spec.tsx- Component testsimageCompression.spec.ts- Compression utility testscameraPermissions.spec.ts- Permission utility tests
- Lazy Loading: Component requests camera only when mounted
- Stream Cleanup: Properly stops all tracks on unmount
- Memory Management: Revokes object URLs after use
- Canvas Optimization: Uses efficient image drawing and compression
- File Size Optimization: Automatically compresses to meet size constraints
| Browser | Min Version | iOS | Android | Desktop |
|---|---|---|---|---|
| Chrome | 47 | - | ✅ | ✅ |
| Firefox | 52 | - | ✅ | ✅ |
| Safari | 11 | ✅ | - | ✅ (10.15+) |
| Edge | 79 | - | - | ✅ |
- HTTPS Only: Camera access requires HTTPS (except localhost)
- User Permission: Users must explicitly grant camera access
- No Data Transmission: Image stays on device until explicitly uploaded
- File Validation: Only JPEG, PNG, and WebP files accepted
- Local Processing: All image compression happens client-side
- Check if HTTPS is enabled (except localhost)
- Verify camera permission is granted in browser settings
- Check if another app is using the camera
- Try a different browser
- Ensure good lighting
- Hold device steady while capturing
- Reduce compression quality setting
- Check device camera resolution
- Go to browser settings and allow camera access
- Clear site data and reload
- Try in a private/incognito window
- Check OS-level camera permissions
- Reduce
compressionQualityprop - Reduce
maxFileSizeand let component auto-compress - Try capturing in lower lighting (less detail to compress)
- Multiple camera selection (if device has >2 cameras)
- Image filters and rotation
- Batch capture mode
- Document detection and auto-crop
- OCR integration for receipt parsing
- Upload progress tracking
- Retry mechanism for failed uploads