'use client'; import { useState, useEffect, useRef } from 'react'; import { X, RotateCw } from 'lucide-react'; interface ImageAdjustmentModalProps { imageUrl: string; onConfirm: (adjustedImage: { rotation: number; cropBounds: { x: number; y: number; width: number; height: number }; imageBlob?: Blob } | null) => void; onCancel: () => void; } export function ImageAdjustmentModal({ imageUrl, onConfirm, onCancel }: ImageAdjustmentModalProps) { const [rotation, setRotation] = useState(0); const [zoom, setZoom] = useState(1); const [minZoom, setMinZoom] = useState(0.1); const [maxZoom, setMaxZoom] = useState(5); const [panX, setPanX] = useState(0); const [panY, setPanY] = useState(0); const [useImage, setUseImage] = useState(true); const [aspectRatio, setAspectRatio] = useState(null); const [cropBounds, setCropBounds] = useState<{ x: number; y: number; width: number; height: number } | null>(null); const canvasRef = useRef(null); const imageRef = useRef(null); const [imageLoaded, setImageLoaded] = useState(false); const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); const [isDraggingCrop, setIsDraggingCrop] = useState<{ type: string; startX: number; startY: number } | null>(null); const [isPanning, setIsPanning] = useState(false); const [lastTouchDistance, setLastTouchDistance] = useState(0); const [lastTouchX, setLastTouchX] = useState(0); const [lastTouchY, setLastTouchY] = useState(0); const aspectRatios = [ { label: 'Free', value: null }, { label: '16:9', value: 16 / 9 }, { label: '4:3', value: 4 / 3 }, { label: '1:1', value: 1 }, { label: '9:16', value: 9 / 16 }, ]; // Load image useEffect(() => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { imageRef.current = img; setImageLoaded(true); setImageDimensions({ width: img.width, height: img.height }); // Initialize crop bounds to full image setCropBounds({ x: 0, y: 0, width: img.width, height: img.height }); // Calculate initial zoom to fit image in canvas (800x600) const canvasWidth = 800; const canvasHeight = 600; const zoomX = canvasWidth / img.width; const zoomY = canvasHeight / img.height; const fitZoom = Math.min(zoomX, zoomY); // Allow any zoom needed to fit setMinZoom(fitZoom); // Min zoom shows entire image setZoom(fitZoom); // Start at fit level }; img.src = imageUrl; }, [imageUrl]); // Draw canvas with image, rotation, crop box useEffect(() => { if (!canvasRef.current || !imageRef.current || !imageLoaded || !cropBounds) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); if (!ctx) return; // Canvas size canvas.width = 800; canvas.height = 600; ctx.fillStyle = '#1e1e1e'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Save state for rotation ctx.save(); ctx.translate(canvas.width / 2, canvas.height / 2); // Apply zoom ctx.scale(zoom, zoom); // Apply pan ctx.translate(panX / zoom, panY / zoom); // Apply rotation ctx.rotate((rotation * Math.PI) / 180); // Draw image centered ctx.drawImage(imageRef.current, -imageDimensions.width / 2, -imageDimensions.height / 2); ctx.restore(); // Draw crop box (in canvas coordinates, accounting for transformations) // This is simplified - for full accuracy, we'd need to transform crop bounds ctx.strokeStyle = '#00FF00'; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.strokeRect(50, 50, 300, 200); // Placeholder - will improve }, [imageLoaded, rotation, zoom, panX, panY, cropBounds]); const handleReset = () => { setRotation(0); setPanX(0); setPanY(0); if (imageRef.current) { setCropBounds({ x: 0, y: 0, width: imageRef.current.width, height: imageRef.current.height }); // Reset to initial fit zoom const canvasWidth = 800; const canvasHeight = 600; const zoomX = canvasWidth / imageRef.current.width; const zoomY = canvasHeight / imageRef.current.height; const fitZoom = Math.min(zoomX, zoomY, 1); setZoom(fitZoom); } }; const handleConfirm = async () => { if (!useImage || !imageRef.current) { console.log('[Modal] User clicked confirm - no image/useImage flag'); onConfirm(null); return; } console.log('=== IMAGE ADJUSTMENT MODAL DEBUG ==='); console.log('[Original] Image dimensions:', imageDimensions); console.log('[User] Rotation:', rotation, '°'); console.log('[User] Use image flag:', useImage); // DON'T process image in frontend - let backend handle rotation // Just send the rotation value, backend will apply it once const { width, height } = imageDimensions; console.log('[Decision] Sending rotation to backend for processing'); console.log('[Decision] NOT processing image in frontend (avoid double rotation)'); console.log('=== END DEBUG ==='); // Return rotation value ONLY - no imageBlob // Backend will apply rotation to original image onConfirm({ rotation, cropBounds: { x: 0, y: 0, width, height } // NO imageBlob - use original from extractedImageBlob }); }; const handleWheel = (e: React.WheelEvent) => { e.preventDefault(); const delta = e.deltaY > 0 ? 0.9 : 1.1; setZoom(prev => Math.max(minZoom, Math.min(maxZoom, prev * delta))); }; const getTouchDistance = (touches: React.TouchList) => { if (touches.length < 2) return 0; const dx = touches[0].clientX - touches[1].clientX; const dy = touches[0].clientY - touches[1].clientY; return Math.sqrt(dx * dx + dy * dy); }; const handleTouchStart = (e: React.TouchEvent) => { if (e.touches.length === 2) { setLastTouchDistance(getTouchDistance(e.touches)); } else if (e.touches.length === 1) { setIsPanning(true); setLastTouchX(e.touches[0].clientX); setLastTouchY(e.touches[0].clientY); } }; const handleTouchMove = (e: React.TouchEvent) => { e.preventDefault(); if (e.touches.length === 2) { // Pinch zoom const newDistance = getTouchDistance(e.touches); if (lastTouchDistance > 0) { const delta = newDistance / lastTouchDistance; setZoom(prev => Math.max(minZoom, Math.min(maxZoom, prev * delta))); setLastTouchDistance(newDistance); } } else if (e.touches.length === 1 && isPanning) { // Single finger pan const touch = e.touches[0]; const dx = touch.clientX - lastTouchX; const dy = touch.clientY - lastTouchY; setPanX(prev => prev + dx); setPanY(prev => prev + dy); setLastTouchX(touch.clientX); setLastTouchY(touch.clientY); } }; const handleTouchEnd = () => { setIsPanning(false); setLastTouchDistance(0); }; return (
{/* Header */}

Rotate Image

{/* Left: Controls */}
{/* Rotation */}
setRotation(Number(e.target.value))} className="w-full" />
{/* Zoom */}
setZoom(Number(e.target.value))} className="w-full" />
{/* Aspect Ratio - DISABLED (cropping not implemented) */} {/*
{aspectRatios.map((ratio) => ( ))}
*/} {/* Reset */}
{/* Center: Canvas */}

Image Preview

Scroll to zoom | Drag to pan | Rotate with slider

{/* Footer */}
); }