import { CameraCapture } from '@/components/CameraCapture';<CameraCapture onCapture={(file) => console.log(file)} /><CameraCapture
onCapture={(file) => handleUpload(file)}
onError={(error) => handleError(error)}
maxFileSize={3 * 1024 * 1024} // 3MB
compressionQuality={0.7} // 0-1
/>| State | UI | Description |
|---|---|---|
idle |
Initial state | Component ready to load camera |
requesting |
Loading spinner | Requesting camera permission |
active |
Camera preview | Camera stream active, ready to capture |
captured |
Image preview | Photo captured, ready to confirm |
error |
Error message | Camera/permission error occurred |
Called when user confirms captured and compressed image.
const handleCapture = (file: File) => {
console.log(file.name); // "receipt.jpg"
console.log(file.type); // "image/jpeg"
console.log(file.size); // bytes
};Called when camera access or compression fails.
const handleError = (error: Error) => {
console.error(error.message);
// Check error.permissionError for detailed error info
if ((error as any).permissionError?.type === 'permission-denied') {
// User denied permission
}
};import { compressImage, formatFileSize } from '@/utils/imageCompression';
const compressed = await compressImage(file, {
maxWidth: 1920,
maxHeight: 1440,
quality: 0.8
});
console.log(formatFileSize(compressed.size)); // "1.2 MB"import {
checkCameraPermission,
requestCameraPermission,
stopCameraStream
} from '@/utils/cameraPermissions';
// Check permission without requesting
const status = await checkCameraPermission(); // "granted" | "denied" | "prompt"
// Request camera access
const stream = await requestCameraPermission({
video: { facingMode: 'environment' },
audio: false
});
// Stop camera
stopCameraStream(stream);const handleCapture = async (file: File) => {
const formData = new FormData();
formData.append('receipt', file);
const response = await fetch('/api/receipts', {
method: 'POST',
body: formData
});
};const [preview, setPreview] = useState<string>('');
const handleCapture = (file: File) => {
setPreview(URL.createObjectURL(file));
// Show preview, let user confirm before upload
};import toast from 'react-hot-toast';
<CameraCapture
onCapture={(file) => {
toast.success('Receipt captured');
}}
onError={(error) => {
toast.error(error.message);
}}
/>const [captures, setCaptures] = useState<File[]>([]);
<CameraCapture
onCapture={(file) => {
setCaptures([...captures, file]);
}}
/>
{captures.map((file, idx) => (
<img
key={idx}
src={URL.createObjectURL(file)}
alt={`Receipt ${idx + 1}`}
/>
))}- Open Safari on iOS device
- Navigate to HTTPS URL
- Allow camera permission when prompted
- Component works with rear and front camera
- Open Chrome on Android device
- Navigate to HTTPS URL
- Allow camera permission in app settings
- Component works with rear and front camera
✓ Check HTTPS is enabled
✓ Verify camera permission is granted
✓ Check if browser supports Camera API
✓ Try different browser
✓ Go to browser/app settings
✓ Grant camera permission
✓ Reload page
✓ Try incognito/private window
✓ Reduce compressionQuality prop
✓ Reduce maxFileSize prop
✓ Try better lighting (less detail)
✓ Hold device steady
✓ Ensure good lighting
✓ Clean camera lens
✓ Reduce compressionQuality
interface CameraCaptureProps {
/**
* Required callback when image is captured and compressed
*/
onCapture: (file: File) => void;
/**
* Optional error handler
*/
onError?: (error: Error) => void;
/**
* Max file size in bytes, default 5MB
* If exceeded, automatically retries with lower quality
*/
maxFileSize?: number;
/**
* JPEG quality 0-1, default 0.8
* Lower = smaller file but worse quality
*/
compressionQuality?: number;
}- Main:
frontend/src/components/CameraCapture/CameraCapture.tsx - Exports:
frontend/src/components/CameraCapture/index.ts
- Compression:
frontend/src/utils/imageCompression.ts - Permissions:
frontend/src/utils/cameraPermissions.ts
- Component:
frontend/src/components/CameraCapture/CameraCapture.spec.tsx - Image Compression:
frontend/src/utils/imageCompression.spec.ts - Permissions:
frontend/src/utils/cameraPermissions.spec.ts
- Full docs:
frontend/src/components/CameraCapture/README.md - Integration examples:
frontend/src/components/CameraCapture/INTEGRATION_GUIDE.md
| Browser | Support | Min Version |
|---|---|---|
| Chrome | ✅ Full | 47 |
| Firefox | ✅ Full | 52 |
| Safari | ✅ Full | 11 (iOS), 10.15 (Mac) |
| Edge | ✅ Full | 79 |
- React 19+
- TypeScript 5.9+
- Tailwind CSS 4.1+
- HTTPS (except localhost)
- Modern browser with Camera API support
- Lazy load - Only mount when needed
- Clean up - Component auto-cleans on unmount
- Compress - Automatic file optimization
- Cache - Reuse compressed files if possible
- Monitor - Check console for warnings
- ✅ HTTPS enforced (except localhost)
- ✅ User permission required
- ✅ No unauthorized access
- ✅ Local processing only
- ✅ Type validation on uploads
For complete documentation, see:
- README.md - Full feature documentation
- INTEGRATION_GUIDE.md - Integration patterns and examples