// frontend/app/(auth)/login/page.jsx "use client"; import { useMemo, useState } from "react"; import { useRouter, useSearchParams } 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"; import { Separator } from "@/components/ui/separator"; const IS_DEV = process.env.NODE_ENV !== "production"; // URL builder กันเคสซ้ำ /api function buildLoginUrl() { const base = (process.env.NEXT_PUBLIC_API_BASE || "").replace(/\/+$/, ""); if (base.endsWith("/api")) return `${base}/auth/login`; return `${base}/api/auth/login`; } // helper: parse response body เป็น json หรือ text async function parseBody(res) { const text = await res.text(); try { return { raw: text, json: JSON.parse(text) }; } catch { return { raw: text, json: null }; } } // สร้างข้อความ debug ที่พร้อม copy function stringifyDebug(debugInfo) { try { return JSON.stringify(debugInfo, null, 2); } catch { return String(debugInfo); } } export default function LoginPage() { const router = useRouter(); const search = useSearchParams(); const redirectTo = search.get("from") || "/dashboard"; const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(""); // สำหรับ debug panel const [debugInfo, setDebugInfo] = useState(null); const [copyState, setCopyState] = useState({ copied: false, error: "" }); const loginUrl = useMemo(buildLoginUrl, [process.env.NEXT_PUBLIC_API_BASE]); async function onSubmit(e) { e.preventDefault(); setSubmitting(true); setError(""); if (IS_DEV) { setDebugInfo(null); setCopyState({ copied: false, error: "" }); } try { const res = await fetch(loginUrl, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ username, password }), }); if (!res.ok) { const body = await parseBody(res); const apiErr = { name: "ApiError", status: res.status, statusText: res.statusText, body: body.json ?? body.raw, message: (() => { const msgFromJson = (body.json && (body.json.error || body.json.message)) || null; if (res.status === 400) return `Bad request: ${msgFromJson ?? res.statusText}`; if (res.status === 401) return `Unauthenticated: ${msgFromJson ?? "Invalid credentials"}`; if (res.status === 403) return `Forbidden: ${msgFromJson ?? res.statusText}`; if (res.status === 404) return `Not found: ${msgFromJson ?? res.statusText}`; if (res.status >= 500) return `Server error (${res.status}): ${ msgFromJson ?? res.statusText }`; return `${res.status} ${res.statusText}: ${ msgFromJson ?? "Request failed" }`; })(), }; if (IS_DEV) { setDebugInfo({ kind: "api", request: { url: loginUrl, method: "POST", payload: { username: "(masked)", password: "(masked)" }, }, response: { status: res.status, statusText: res.statusText, body: apiErr.body, }, env: { NEXT_PUBLIC_API_BASE: process.env.NEXT_PUBLIC_API_BASE || "(unset)", NODE_ENV: process.env.NODE_ENV, }, }); } throw apiErr; } // ✅ สำเร็จ if (IS_DEV) { setDebugInfo({ kind: "success", request: { url: loginUrl, method: "POST" }, note: "Login success. Redirecting…", }); } router.push(redirectTo); } catch (err) { if (err?.name === "ApiError") { setError(err.message); } else if (err instanceof TypeError && /fetch/i.test(err.message)) { setError( "Network error: ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้ (ตรวจสอบ proxy/NPM/SSL)" ); if (IS_DEV) { setDebugInfo({ kind: "network", request: { url: loginUrl, method: "POST" }, error: { message: err.message }, hint: "เช็คว่า NPM ชี้ proxy /api ไปที่ backend ถูก network/port, และ TLS chain ถูกต้อง", }); } } else { setError(err?.message || "Unexpected error"); if (IS_DEV) { setDebugInfo({ kind: "unknown", request: { url: loginUrl, method: "POST" }, error: { message: String(err) }, }); } } } finally { setSubmitting(false); } } async function handleCopyDebug() { if (!debugInfo) return; const text = stringifyDebug(debugInfo); try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); } else { // Fallback const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.left = "-9999px"; document.body.appendChild(ta); ta.focus(); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); } setCopyState({ copied: true, error: "" }); setTimeout(() => setCopyState({ copied: false, error: "" }), 1500); } catch (e) { setCopyState({ copied: false, error: "คัดลอกไม่สำเร็จ (permission ของ clipboard?)", }); setTimeout(() => setCopyState({ copied: false, error: "" }), 2500); } } return ( Sign in Enter your credentials to access the DMS {error ? ( {error} ) : null}
setUsername(e.target.value)} required />
setPassword(e.target.value)} required />
{IS_DEV && (
Debug (dev mode only)
Request URL:{" "} {loginUrl}
{debugInfo?.request?.method && (
Method:{" "} {debugInfo.request.method}
)} {debugInfo?.response && ( <>
Status:{" "} {debugInfo.response.status}{" "} {debugInfo.response.statusText}
Response body:
                    {typeof debugInfo.response.body === "string"
                      ? debugInfo.response.body
                      : JSON.stringify(debugInfo.response.body, null, 2)}
                  
)} {debugInfo?.error && ( <>
Error:
                    {JSON.stringify(debugInfo.error, null, 2)}
                  
)} {debugInfo?.env && ( <>
Env:
                    {JSON.stringify(debugInfo.env, null, 2)}
                  
)} {debugInfo?.note && (
{debugInfo.note}
)} {debugInfo?.hint && (
Hint: {debugInfo.hint}
)} {copyState.error && (
{copyState.error}
)}
)}
© {new Date().getFullYear()} np-dms.work
); }