Files
buzz-sheet/components/admin/media-uploader.tsx
T
gunshiz e0794822b1
CI / Verify (push) Successful in 1m18s
CI / Build immutable images and deploy (push) Successful in 1m57s
feat(i18n) : add personal languages and compact weapon exports
2026-09-14 16:44:44 +07:00

97 lines
3.0 KiB
TypeScript

"use client";
import { useTranslation } from "@/components/i18n/locale-provider";
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,
}: {
onReady: (media: UploadedMedia) => void;
onFileSelected?: (file: File) => void;
}) {
const { t } = useTranslation();
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(t("รองรับเฉพาะ PNG, JPEG, WebP และ GIF"));
return;
}
if (file.size <= 0 || file.size > MAX_MEDIA_BYTES) {
toast.error(t("ไฟล์ต้องมีขนาดไม่เกิน 100 MB"));
return;
}
try {
setState("uploading");
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(t("อัปโหลดรูปภาพแล้ว"));
} catch (cause) {
toast.error(t(cause instanceof Error ? cause.message : "อัปโหลดไม่สำเร็จ"));
} finally {
setState("idle");
}
}
return (
<Field>
<FieldLabel htmlFor={inputId}>{t("คลังสื่อ")}</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" && t("กำลังอัปโหลด…")}
{state === "idle" && t("อัปโหลดและเพิ่มรูปภาพ")}
{!busy ? <UploadCloudIcon className="ml-auto" /> : null}
</Button>
<FieldDescription>{t("PNG, JPEG, WebP หรือ GIF ขนาดไม่เกิน 100 MB")}</FieldDescription>
</Field>
);
}