CropModals: two different for now because of aspect ratios - maybe merge into one

This commit is contained in:
seaznCode 2025-12-06 20:30:33 +01:00
parent 20c71636f6
commit 1c87ba150e
2 changed files with 264 additions and 0 deletions

View File

@ -0,0 +1,132 @@
'use client'
import React, { useState, useCallback } from 'react'
import Cropper from 'react-easy-crop'
import { Point, Area } from 'react-easy-crop'
interface AffiliateCropModalProps {
isOpen: boolean
imageSrc: string
onClose: () => void
onCropComplete: (croppedImageBlob: Blob) => void
}
export default function AffiliateCropModal({ isOpen, imageSrc, onClose, onCropComplete }: AffiliateCropModalProps) {
const [crop, setCrop] = useState<Point>({ x: 0, y: 0 })
const [zoom, setZoom] = useState(1)
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null)
const onCropAreaComplete = useCallback((_croppedArea: Area, croppedAreaPixels: Area) => {
setCroppedAreaPixels(croppedAreaPixels)
}, [])
const createCroppedImage = async () => {
if (!croppedAreaPixels) return
const image = new Image()
image.src = imageSrc
await new Promise((resolve) => {
image.onload = resolve
})
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx) return
// Set canvas size to cropped area
canvas.width = croppedAreaPixels.width
canvas.height = croppedAreaPixels.height
ctx.drawImage(
image,
croppedAreaPixels.x,
croppedAreaPixels.y,
croppedAreaPixels.width,
croppedAreaPixels.height,
0,
0,
croppedAreaPixels.width,
croppedAreaPixels.height
)
return new Promise<Blob>((resolve) => {
canvas.toBlob((blob) => {
if (blob) resolve(blob)
}, 'image/jpeg', 0.95)
})
}
const handleSave = async () => {
const croppedBlob = await createCroppedImage()
if (croppedBlob) {
onCropComplete(croppedBlob)
onClose()
}
}
if (!isOpen) return null
return (
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/70">
<div className="relative w-full max-w-4xl mx-4 bg-white rounded-2xl shadow-2xl overflow-hidden">
{/* Header */}
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between bg-gradient-to-r from-blue-50 to-white">
<h2 className="text-xl font-semibold text-blue-900">Crop Affiliate Logo</h2>
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 transition"
aria-label="Close"
>
</button>
</div>
{/* Crop Area */}
<div className="relative bg-gray-900" style={{ height: '500px' }}>
<Cropper
image={imageSrc}
crop={crop}
zoom={zoom}
aspect={3 / 2}
onCropChange={setCrop}
onZoomChange={setZoom}
onCropComplete={onCropAreaComplete}
/>
</div>
{/* Controls */}
<div className="px-6 py-4 border-t border-gray-200 bg-gray-50">
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-blue-900 mb-2">
Zoom: {zoom.toFixed(1)}x
</label>
<input
type="range"
min={1}
max={3}
step={0.1}
value={zoom}
onChange={(e) => setZoom(Number(e.target.value))}
className="w-full h-2 bg-blue-200 rounded-lg appearance-none cursor-pointer accent-blue-900"
/>
</div>
<div className="flex items-center justify-end gap-3">
<button
onClick={onClose}
className="px-5 py-2.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition"
>
Cancel
</button>
<button
onClick={handleSave}
className="px-5 py-2.5 text-sm font-semibold text-white bg-blue-900 rounded-lg hover:bg-blue-800 shadow transition"
>
Apply Crop
</button>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,132 @@
'use client'
import React, { useState, useCallback } from 'react'
import Cropper from 'react-easy-crop'
import { Point, Area } from 'react-easy-crop'
interface ImageCropModalProps {
isOpen: boolean
imageSrc: string
onClose: () => void
onCropComplete: (croppedImageBlob: Blob) => void
}
export default function ImageCropModal({ isOpen, imageSrc, onClose, onCropComplete }: ImageCropModalProps) {
const [crop, setCrop] = useState<Point>({ x: 0, y: 0 })
const [zoom, setZoom] = useState(1)
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null)
const onCropAreaComplete = useCallback((_croppedArea: Area, croppedAreaPixels: Area) => {
setCroppedAreaPixels(croppedAreaPixels)
}, [])
const createCroppedImage = async () => {
if (!croppedAreaPixels) return
const image = new Image()
image.src = imageSrc
await new Promise((resolve) => {
image.onload = resolve
})
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx) return
// Set canvas size to cropped area
canvas.width = croppedAreaPixels.width
canvas.height = croppedAreaPixels.height
ctx.drawImage(
image,
croppedAreaPixels.x,
croppedAreaPixels.y,
croppedAreaPixels.width,
croppedAreaPixels.height,
0,
0,
croppedAreaPixels.width,
croppedAreaPixels.height
)
return new Promise<Blob>((resolve) => {
canvas.toBlob((blob) => {
if (blob) resolve(blob)
}, 'image/jpeg', 0.95)
})
}
const handleSave = async () => {
const croppedBlob = await createCroppedImage()
if (croppedBlob) {
onCropComplete(croppedBlob)
onClose()
}
}
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70">
<div className="relative w-full max-w-4xl mx-4 bg-white rounded-2xl shadow-2xl overflow-hidden">
{/* Header */}
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between bg-gradient-to-r from-blue-50 to-white">
<h2 className="text-xl font-semibold text-blue-900">Crop & Adjust Image</h2>
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 transition"
aria-label="Close"
>
</button>
</div>
{/* Crop Area */}
<div className="relative bg-gray-900" style={{ height: '500px' }}>
<Cropper
image={imageSrc}
crop={crop}
zoom={zoom}
aspect={16 / 9}
onCropChange={setCrop}
onZoomChange={setZoom}
onCropComplete={onCropAreaComplete}
/>
</div>
{/* Controls */}
<div className="px-6 py-4 border-t border-gray-200 bg-gray-50">
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-blue-900 mb-2">
Zoom: {zoom.toFixed(1)}x
</label>
<input
type="range"
min={1}
max={3}
step={0.1}
value={zoom}
onChange={(e) => setZoom(Number(e.target.value))}
className="w-full h-2 bg-blue-200 rounded-lg appearance-none cursor-pointer accent-blue-900"
/>
</div>
<div className="flex items-center justify-end gap-3">
<button
onClick={onClose}
className="px-5 py-2.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition"
>
Cancel
</button>
<button
onClick={handleSave}
className="px-5 py-2.5 text-sm font-semibold text-white bg-blue-900 rounded-lg hover:bg-blue-800 shadow transition"
>
Apply Crop
</button>
</div>
</div>
</div>
</div>
</div>
)
}