99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
"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;
|
|
url: string;
|
|
fileName: string;
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
export function MediaUploader({
|
|
onReady,
|
|
onFileSelected,
|
|
onUploadingChange,
|
|
}: {
|
|
onReady: (media: UploadedMedia) => void;
|
|
onFileSelected?: (file: File) => void;
|
|
onUploadingChange?: (uploading: boolean) => void;
|
|
}) {
|
|
const inputId = useId();
|
|
const [state, setState] = useState<"idle" | "uploading">("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("ไฟล์ต้องมีขนาดไม่เกิน 100 MB");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setState("uploading");
|
|
onUploadingChange?.(true);
|
|
const body = new FormData();
|
|
body.set("file", file);
|
|
const response = await fetch("/api/admin/media", {
|
|
method: "POST",
|
|
body,
|
|
});
|
|
const result = await response.json() as UploadedMedia & { error?: string };
|
|
if (!response.ok) throw new Error(result.error || "upload");
|
|
onReady(result);
|
|
toast.success("อัปโหลดรูปภาพแล้ว");
|
|
} catch (cause) {
|
|
toast.error(cause instanceof Error ? cause.message : "อัปโหลดไม่สำเร็จ");
|
|
} finally {
|
|
setState("idle");
|
|
onUploadingChange?.(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Field>
|
|
<FieldLabel htmlFor={inputId}>คลังสื่อ</FieldLabel>
|
|
<input
|
|
id={inputId}
|
|
type="file"
|
|
className="sr-only"
|
|
accept={IMAGE_MIME_TYPES.join(",")}
|
|
disabled={busy}
|
|
onChange={(event) => {
|
|
const file = event.target.files?.[0];
|
|
event.target.value = "";
|
|
if (file) {
|
|
onFileSelected?.(file);
|
|
void upload(file);
|
|
}
|
|
}}
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
className="w-full"
|
|
disabled={busy}
|
|
render={<label htmlFor={inputId} />}
|
|
nativeButton={false}
|
|
>
|
|
{busy ? <Spinner /> : <ImagePlusIcon />}
|
|
{state === "uploading" && "กำลังอัปโหลด…"}
|
|
{state === "idle" && "อัปโหลดและเพิ่มรูปภาพ"}
|
|
{!busy ? <UploadCloudIcon className="ml-auto" /> : null}
|
|
</Button>
|
|
<FieldDescription>PNG, JPEG, WebP หรือ GIF ขนาดไม่เกิน 100 MB</FieldDescription>
|
|
</Field>
|
|
);
|
|
}
|