// File: frontend/app/(auth)/login/page.jsx "use client"; import { useState, useMemo, Suspense } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Alert, AlertDescription } from "@/components/ui/alert"; const API_BASE = (process.env.NEXT_PUBLIC_API_BASE || "").replace(/\/$/, ""); const DEBUG = String(process.env.NEXT_PUBLIC_DEBUG_AUTH || "").trim() !== "" && process.env.NEXT_PUBLIC_DEBUG_AUTH !== "0" && process.env.NEXT_PUBLIC_DEBUG_AUTH !== "false"; function dlog(...args) { if (DEBUG && typeof window !== "undefined") console.debug("[login]", ...args); } function LoginForm() { const router = useRouter(); const searchParams = useSearchParams(); const nextPath = useMemo(() => searchParams.get("next") || "/dashboard", [searchParams]); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [showPw, setShowPw] = useState(false); const [submitting, setSubmitting] = useState(false); const [err, setErr] = useState(""); // Helper function to verify session is ready after login async function verifySessionIsReady() { const MAX_RETRIES = 5; const RETRY_DELAY = 300; // ms for (let i = 0; i < MAX_RETRIES; i++) { const me = await fetch(`${API_BASE}/api/auth/me`, { method: "GET", credentials: "include", cache: "no-store", }).then(r => r.ok ? r.json() : null).catch(() => null); if (me?.ok) return true; await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); } return false; } async function onSubmit(e) { e.preventDefault(); setErr(""); if (!username.trim() || !password) { setErr("กรอกชื่อผู้ใช้และรหัสผ่านให้ครบ"); return; } try { setSubmitting(true); dlog("API_BASE =", API_BASE || "(empty → relative path)"); dlog("nextPath =", nextPath); const res = await fetch(`${API_BASE}/api/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", // << ใช้คุกกี้ cache: "no-store", body: JSON.stringify({ username, password }), }); dlog("status =", res.status, "ctype =", res.headers.get("content-type")); let data = {}; try { data = await res.json(); } catch {} if (!res.ok) { const msg = data?.error === "INVALID_CREDENTIALS" ? "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง" : data?.error || `เข้าสู่ระบบไม่สำเร็จ (HTTP ${res.status})`; setErr(msg); return; } // ✅ ยืนยันว่าเซสชันพร้อมใช้งานก่อน (กัน redirect วน) // ✅ รอ session ให้พร้อมจริง (retry สูงสุด ~1.5s) const ok = await verifySessionIsReady(); if (!ok) { setErr("ล็อกอินสำเร็จ แต่ยังไม่เห็นเซสชันจากเซิร์ฟเวอร์ (ลองใหม่หรือตรวจคุกกี้)"); return; } // ✅ ใช้ hard navigation ให้ SSR เห็นคุกกี้แน่นอน if (typeof window !== "undefined") { window.location.href = nextPath || "/dashboard"; } else { router.replace(nextPath || "/dashboard"); } } catch (e) { dlog("exception =", e); setErr("เชื่อมต่อเซิร์ฟเวอร์ไม่ได้ กรุณาลองใหม่"); } finally { setSubmitting(false); } } return (
เข้าสู่ระบบ Document Management System • LCBP3 {err ? ( {err} ) : null}
setUsername(e.target.value)} placeholder="เช่น superadmin" disabled={submitting} />
setPassword(e.target.value)} placeholder="••••••••" disabled={submitting} className="pr-10" />
{DEBUG ? (

DEBUG: NEXT_PUBLIC_API_BASE = {API_BASE || "(empty)"}

) : null}
© {new Date().getFullYear()} np-dms.work
); } export default function LoginPage() { return ( }> ); } function LoginPageSkeleton() { return (
เข้าสู่ระบบ Document Management System • LCBP3 {/* ✅ ปรับปรุง Skeleton ให้สมจริงขึ้น */}
{/* ✅ เพิ่ม Skeleton สำหรับ Footer */}
); } function Spinner() { return ( ); }