"use client"; import { ImagePlusIcon, UploadCloudIcon } from "lucide-react"; import { useId, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"; import { Spinner } from "@/components/ui/spinner"; import { IMAGE_MIME_TYPES, MAX_MEDIA_BYTES } from "@/lib/media/validation"; export interface UploadedMedia { id: string; fileName: string; mimeType: string; byteSize: number; url: string; } export function MediaUploader({ onReady, }: { onReady: (media: UploadedMedia) => void; }) { const inputId = useId(); const [state, setState] = useState<"idle" | "preparing" | "uploading" | "checking">("idle"); const busy = state !== "idle"; async function upload(file: File) { if (!IMAGE_MIME_TYPES.includes(file.type as (typeof IMAGE_MIME_TYPES)[number])) { toast.error("รองรับเฉพาะ PNG, JPEG, WebP และ GIF"); return; } if (file.size <= 0 || file.size > MAX_MEDIA_BYTES) { toast.error("ไฟล์ต้องมีขนาดไม่เกิน 20 MB"); return; } let mediaId: string | undefined; try { setState("preparing"); const presignResponse = await fetch("/api/admin/media/presign", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ fileName: file.name, mimeType: file.type, byteSize: file.size }), }); if (!presignResponse.ok) throw new Error("presign"); const upload = await presignResponse.json() as { mediaId: string; uploadUrl: string; headers: Record; }; mediaId = upload.mediaId; setState("uploading"); const putResponse = await fetch(upload.uploadUrl, { method: "PUT", headers: upload.headers, body: file, }); if (!putResponse.ok) throw new Error("upload"); setState("checking"); const completeResponse = await fetch(`/api/admin/media/${mediaId}/complete`, { method: "POST" }); if (!completeResponse.ok) throw new Error("complete"); const result = await completeResponse.json() as { media: UploadedMedia }; onReady(result.media); toast.success("อัปโหลดรูปภาพแล้ว"); } catch { if (mediaId) { await fetch(`/api/admin/media/${mediaId}`, { method: "DELETE" }).catch(() => undefined); } toast.error("อัปโหลดไม่สำเร็จ ไฟล์ชั่วคราวถูกเก็บกวาดแล้ว"); } finally { setState("idle"); } } return ( คลังสื่อ { const file = event.target.files?.[0]; event.target.value = ""; if (file) void upload(file); }} /> PNG, JPEG, WebP หรือ GIF ขนาดไม่เกิน 20 MB ); }