"use client"; // ฟอร์ม login (mock e-Court) — เรียก server action login แล้ว router.replace(next ?? "/note") // (pattern ตาม UploadFileForm: useTransition + error state + ข้อความ fallback ตอน transport fail) // next = path วนกลับหลัง login (N8 D5 — ผ่าน sanitizeNextPath จากหน้า login มาแล้ว) // Genesis (docs/DESIGN.md): การ์ด surface 12px radius ขอบ line ไม่มีเงาระดับ static / // input ผ่าน .genesis-input / ปุ่ม primary hover ขึ้น 1px + glow ฟ้า (shadow-btn-glow) import { useRef, useState, useTransition } from "react"; import { useRouter } from "next/navigation"; import { login } from "@/app/actions/auth"; import Select from "@/components/Select"; import { MOCK_USERS } from "@/utils/user"; // text input ของแอป — text-base (16px) เสมอ: กัน iOS auto-zoom ตอน focus บน iPad // (ทับ text-sm ของ .genesis-input — utility อยู่หลัง @layer components ชนะเสมอ) // error ตาม spec Inputs: ขอบเปลี่ยนเป็นแดง (ring ยังเป็นฟ้าตาม focus ring หลัก) const INPUT_CLASS = "genesis-input mt-1 block w-full text-base transition aria-invalid:border-error"; // dropdown ผู้ใช้ — เหมือน INPUT_CLASS แต่ไม่มี mt-1 (Select ครอบ div.relative: มาร์จินที่ wrapper ข้างนอกแทน) const SELECT_CLASS = "genesis-input block w-full text-base transition"; export default function LoginForm({ next }: { next: string | null }) { const router = useRouter(); const passwordRef = useRef(null); const [error, setError] = useState(null); const [pending, startTransition] = useTransition(); const onSubmit = (e: React.FormEvent) => { e.preventDefault(); const form = e.currentTarget; setError(null); const usernameInput = form.elements.namedItem("username") as HTMLSelectElement; const passwordInput = form.elements.namedItem("password") as HTMLInputElement; startTransition(async () => { try { const result = await login(usernameInput.value, passwordInput.value); if (result.ok) { router.replace(next ?? "/note"); } else { setError(result.error); // คง username ไว้ (โอกาสพลาดอยู่ที่ password) — ล้างแล้วโฟกัสให้พิมพ์ทับได้ทันที passwordInput.value = ""; passwordRef.current?.focus(); passwordRef.current?.select(); } } catch { setError("เข้าสู่ระบบไม่สำเร็จ — ลองใหม่อีกครั้ง"); } }); }; return (
{/* brand lockup เดียวกับ nav หน้ารายการ — tile JN น้ำเงินคือแบรนด์มาร์กของระบบ (หน้าสรุปแถบน้ำเงิน 2026-09-17 สืบต่อจาก tile นี้) ชื่อแบรนด์เป็น ink ตามกฎ "blue เฉพาะของ interactive" ของ Genesis */}
JN

Judge's Note

ระบบ PDF Annotation สำหรับ e-Court

เข้าสู่ระบบ

{/* รหัสผ่านทดสอบ — รายชื่อเห็นใน dropdown แล้ว เหลือบอกแค่รหัสผ่าน (mock ไม่มีระบบสมัคร) กล่อง metadata 8px radius ตาม Genesis (พื้นเทาโปร่งจาง ไม่ใช่การเน้น) */}

รหัสผ่านทดสอบ:{" "} Demo1234

{error && (

{error}

)}
); }