feat(29-03): integrate crop editor into ImageUpload
Show ImageCropEditor after successful upload when onCropChange callback is provided. Editor replaces image preview temporarily. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { apiUpload } from "../lib/api";
|
||||
import { GearImage, imageContainerBg } from "./GearImage";
|
||||
import { ImageCropEditor } from "./ImageCropEditor";
|
||||
|
||||
interface ImageUploadProps {
|
||||
value: string | null;
|
||||
imageUrl?: string | null;
|
||||
dominantColor?: string | null;
|
||||
onChange: (filename: string | null) => void;
|
||||
onCropChange?: (crop: { zoom: number; x: number; y: number }) => void;
|
||||
}
|
||||
|
||||
const MAX_SIZE_BYTES = 5 * 1024 * 1024; // 5MB
|
||||
@@ -17,10 +19,12 @@ export function ImageUpload({
|
||||
imageUrl,
|
||||
dominantColor,
|
||||
onChange,
|
||||
onCropChange,
|
||||
}: ImageUploadProps) {
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [localPreview, setLocalPreview] = useState<string | null>(null);
|
||||
const [showCropEditor, setShowCropEditor] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
async function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
@@ -47,6 +51,9 @@ export function ImageUpload({
|
||||
try {
|
||||
const result = await apiUpload<{ filename: string }>("/api/images", file);
|
||||
onChange(result.filename);
|
||||
if (onCropChange) {
|
||||
setShowCropEditor(true);
|
||||
}
|
||||
} catch {
|
||||
setError("Upload failed. Please try again.");
|
||||
setLocalPreview(null);
|
||||
@@ -69,7 +76,23 @@ export function ImageUpload({
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Crop editor overlay */}
|
||||
{showCropEditor && displayUrl && onCropChange && (
|
||||
<div className="mb-4">
|
||||
<ImageCropEditor
|
||||
imageUrl={displayUrl}
|
||||
dominantColor={dominantColor}
|
||||
onSave={(result) => {
|
||||
onCropChange(result);
|
||||
setShowCropEditor(false);
|
||||
}}
|
||||
onCancel={() => setShowCropEditor(false)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hero image area */}
|
||||
{!showCropEditor && (
|
||||
<div
|
||||
onClick={() => inputRef.current?.click()}
|
||||
className="relative w-full aspect-[4/3] rounded-xl overflow-hidden cursor-pointer group"
|
||||
@@ -154,6 +177,7 @@ export function ImageUpload({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
ref={inputRef}
|
||||
|
||||
Reference in New Issue
Block a user