satupeta-main/shared/components/image-upload.tsx
2026-01-27 09:31:12 +07:00

135 lines
3.8 KiB
TypeScript

"use client";
import { UploadCloud, X } from "lucide-react";
import Image from "next/image";
import { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
import { toast } from "sonner";
import { Button } from "./ui/button";
import fileApi, { FileUploadResponse } from "../services/file";
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
interface ImageUploadProps {
value?: string;
onChange: (value: string) => void;
onRemove: () => void;
description?: string;
}
export function ImageUpload({
value,
onChange,
onRemove,
description,
}: ImageUploadProps) {
const [isUploading, setIsUploading] = useState(false);
const [, setImageError] = useState(false);
const onDrop = useCallback(
async (acceptedFiles: File[]) => {
const file = acceptedFiles[0];
if (!file) return;
try {
setIsUploading(true);
setImageError(false);
const response: FileUploadResponse = await fileApi.uploadFile(
file,
description
);
if (!response.id) {
throw new Error("No file ID received from server");
}
onChange(response.id);
toast.success("Gambar berhasil diunggah");
} catch (error) {
console.error("Error uploading image:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengunggah gambar"
);
} finally {
setIsUploading(false);
}
},
[onChange, description]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
"image/*": [".png", ".jpg", ".jpeg", ".gif"],
},
maxFiles: 1,
maxSize: 5 * 1024 * 1024, // 5MB
disabled: isUploading,
});
const imageUrl = value ? `${API_URL}/files/${value}/download` : undefined;
return (
<div className="space-y-4">
{value ? (
<div className="flex items-center gap-4">
<div className="relative h-20 w-20 overflow-hidden rounded-lg">
<Image
src={imageUrl!}
alt="Uploaded image"
fill
className="object-cover"
onError={() => {
setImageError(true);
toast.error("Gagal memuat gambar");
}}
unoptimized
/>
<Button
type="button"
variant="destructive"
size="icon"
className="absolute right-1 top-1 h-6 w-6"
onClick={onRemove}
disabled={isUploading}
>
<X className="h-3 w-3" />
</Button>
</div>
<div className="flex-1">
<p className="text-sm font-medium">Gambar berhasil diunggah</p>
<p className="text-xs text-muted-foreground">
Klik untuk mengubah gambar
</p>
</div>
</div>
) : (
<div
{...getRootProps()}
className={`flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed p-4 transition-colors ${
isDragActive ? "border-primary bg-primary/5" : "border-muted"
} ${isUploading ? "opacity-50 cursor-not-allowed" : ""}`}
>
<input {...getInputProps()} />
<UploadCloud className="mb-2 h-10 w-10 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
{isUploading
? "Mengunggah..."
: isDragActive
? "Drop the image here"
: "Drag & drop an image here, or click to select"}
</p>
<p className="text-xs text-muted-foreground">
Max file size: 5MB. Supported formats: PNG, JPG, JPEG, GIF
</p>
</div>
)}
</div>
);
}