"use client"; import Image from "next/image"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useRef, useState } from "react"; import { FileUp, ImagePlus, Upload, X } from "lucide-react"; import { toast } from "sonner"; import { createShareAction } from "@/app/actions/share"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel, } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Spinner } from "@/components/ui/spinner"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { MAX_SHARE_DESCRIPTION_LENGTH, MAX_SHARE_IMAGE_BYTES, MAX_SHARE_TEXT_BYTES, } from "@/lib/share/limits"; const IMAGE_TYPES = "image/png,image/jpeg,image/webp,image/gif"; function formatBytes(bytes: number) { if (bytes < 1024) return `${bytes} B`; return `${(bytes / 1024 / 1024).toFixed(2)} MiB`; } export function ShareUploadClient() { const router = useRouter(); const textFileRef = useRef(null); const imageFileRef = useRef(null); const [text, setText] = useState(""); const [description, setDescription] = useState(""); const [image, setImage] = useState(null); const [imagePreview, setImagePreview] = useState(null); const [dragging, setDragging] = useState(false); const [pending, setPending] = useState(false); const [error, setError] = useState(null); const byteCount = useMemo(() => new TextEncoder().encode(text).byteLength, [text]); useEffect(() => { if (!imagePreview) return; return () => URL.revokeObjectURL(imagePreview); }, [imagePreview]); async function loadTextFile(file: File) { setError(null); if (file.size > MAX_SHARE_TEXT_BYTES) { setError("ไฟล์ต้องมีขนาดไม่เกิน 5 MiB"); return; } try { const content = new TextDecoder("utf-8", { fatal: true }).decode( await file.arrayBuffer() ); setText(content); toast.success(`เปิด ${file.name} แล้ว`); } catch { setError("ไฟล์ต้องเป็นข้อความ UTF-8 ที่ถูกต้อง"); } } function chooseImage(file: File | undefined) { setError(null); if (!file) return; if (!IMAGE_TYPES.split(",").includes(file.type)) { setError("รูปภาพต้องเป็น PNG, JPEG, WebP หรือ GIF"); return; } if (file.size > MAX_SHARE_IMAGE_BYTES) { setError("รูปภาพต้องมีขนาดไม่เกิน 50 MiB"); return; } setImage(file); setImagePreview(URL.createObjectURL(file)); } async function submit(event: React.FormEvent) { event.preventDefault(); setError(null); if (!text.trim()) { setError("วางข้อความก่อนสร้างลิงก์"); return; } if (byteCount > MAX_SHARE_TEXT_BYTES) { setError("ข้อความต้องมีขนาดไม่เกิน 5 MiB"); return; } setPending(true); const formData = new FormData(); formData.set("text", text); formData.set("description", description); if (image) formData.set("image", image); try { const result = await createShareAction(formData); if (!result.success || !result.id) { setError(result.success ? "สร้างลิงก์ไม่สำเร็จ" : result.error); return; } router.push(`/share/${result.id}`); } catch { setError("เชื่อมต่อเซิร์ฟเวอร์ไม่สำเร็จ ลองใหม่อีกครั้ง"); } finally { setPending(false); } } return (
share://new วางข้อความหรือเลือกไฟล์ แล้วรับลิงก์สำหรับส่งต่อ
{formatBytes(byteCount)} / 5 MiB
{ event.preventDefault(); setDragging(true); }} onDragOver={(event) => event.preventDefault()} onDragLeave={(event) => { if (!event.currentTarget.contains(event.relatedTarget as Node)) { setDragging(false); } }} onDrop={(event) => { event.preventDefault(); setDragging(false); const file = event.dataTransfer.files[0]; if (file) void loadTextFile(file); }} > ข้อความที่ต้องการแชร์