import React, { useState, useCallback, useEffect } from 'react'; import Cropper from 'react-easy-crop'; import type { Area } from 'react-easy-crop'; import { cropImage } from '../../utils/cropImage'; interface ImageCropModalProps { isOpen: boolean; onClose: () => void; imageSrc: string; onCropComplete: (blob: Blob) => void; title?: string; aspectRatio?: number; cropShape?: 'round' | 'rect'; maxOutputDimension?: number; outputType?: string; } export function ImageCropModal({ isOpen, onClose, imageSrc, onCropComplete, title = 'Crop Image', aspectRatio = 1, cropShape = 'round', maxOutputDimension, outputType, }: ImageCropModalProps) { const [crop, setCrop] = useState({ x: 0, y: 0 }); const [zoom, setZoom] = useState(1); const [croppedAreaPixels, setCroppedAreaPixels] = useState(null); const [isProcessing, setIsProcessing] = useState(false); const handleCropComplete = useCallback((_croppedArea: Area, croppedPixels: Area) => { setCroppedAreaPixels(croppedPixels); }, []); const handleApply = async () => { if (!croppedAreaPixels) return; setIsProcessing(true); try { const blob = await cropImage(imageSrc, croppedAreaPixels, outputType ?? 'image/webp', { maxDimension: maxOutputDimension, outputType, }); onCropComplete(blob); onClose(); } catch { // Silently fail — user can retry } finally { setIsProcessing(false); } }; // Reset state when opened with a new image useEffect(() => { if (isOpen) { setCrop({ x: 0, y: 0 }); setZoom(1); setCroppedAreaPixels(null); setIsProcessing(false); } }, [isOpen, imageSrc]); useEffect(() => { if (!isOpen) return; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose]); if (!isOpen) return null; return (