Files
Rs_blog_front/src/components/editor/ImageUpload.tsx
2026-02-11 19:46:38 +08:00

120 lines
3.0 KiB
TypeScript

"use client";
import { useState, useRef, type DragEvent, type ChangeEvent } from "react";
import clsx from "clsx";
import { api } from "@/lib/api";
interface ImageUploadProps {
onUpload: (url: string) => void;
}
const ACCEPTED_TYPES = ["image/jpeg", "image/png", "image/webp"];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
export function ImageUpload({ onUpload }: ImageUploadProps) {
const [isDragging, setIsDragging] = useState(false);
const [uploading, setUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const validateFile = (file: File): string | null => {
if (!ACCEPTED_TYPES.includes(file.type)) {
return "Only JPG, PNG, and WebP files are allowed.";
}
if (file.size > MAX_SIZE) {
return "File size must be under 5MB.";
}
return null;
};
const uploadFile = async (file: File) => {
const validationError = validateFile(file);
if (validationError) {
setError(validationError);
setTimeout(() => setError(null), 3000);
return;
}
setError(null);
setUploading(true);
try {
const response = await api.uploadImage(file);
onUpload(response.url);
} catch {
setError("Upload failed. Please try again.");
setTimeout(() => setError(null), 3000);
} finally {
setUploading(false);
}
};
const handleDragOver = (e: DragEvent) => {
e.preventDefault();
setIsDragging(true);
};
const handleDragLeave = (e: DragEvent) => {
e.preventDefault();
setIsDragging(false);
};
const handleDrop = (e: DragEvent) => {
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files[0];
if (file) {
uploadFile(file);
}
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
uploadFile(file);
}
};
const handleClick = () => {
inputRef.current?.click();
};
return (
<div
onClick={handleClick}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={clsx(
"border-2 border-dashed p-8 text-center cursor-pointer transition-colors",
isDragging && "border-black bg-neutral-50",
error && "border-red-500 animate-shake",
!isDragging && !error && "border-neutral-300 hover:border-neutral-400"
)}
>
<input
ref={inputRef}
type="file"
accept={ACCEPTED_TYPES.join(",")}
onChange={handleChange}
className="hidden"
/>
{uploading ? (
<p className="text-sm text-neutral-500">Uploading...</p>
) : error ? (
<p className="text-sm text-red-500">{error}</p>
) : (
<p className="text-sm text-neutral-500">
Drop an image here or click to upload
</p>
)}
<p className="text-xs text-neutral-400 mt-2">
JPG, PNG, WebP up to 5MB
</p>
</div>
);
}