"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import Image from "next/image"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { LanguageSelect } from "@/components/LanguageSelect"; import { ThemeSelect } from "@/components/ThemeSelect"; import { transformApiMenusToMenuItems } from "@/lib/utils/menuTransform"; import { User, Lock, Eye, EyeOff, ArrowRight } from "lucide-react"; import { isNextRedirectError } from '@/lib/utils/redirect-error'; export function LoginPage() { const router = useRouter(); const t = useTranslations('auth'); const tCommon = useTranslations('common'); const tValidation = useTranslations('validation'); const [userId, setUserId] = useState(process.env.NEXT_PUBLIC_DEV_USER_ID || ""); const [password, setPassword] = useState(process.env.NEXT_PUBLIC_DEV_USER_PWD || ""); const [showPassword, setShowPassword] = useState(false); const [rememberMe, setRememberMe] = useState(false); const [error, setError] = useState(""); // 2025-11-27: isChecking 상태 제거 - 미들웨어에서 인증 체크하므로 불필요 // const [isChecking, setIsChecking] = useState(true); const [isLoggingIn, setIsLoggingIn] = useState(false); // ✅ 로그인 진행 중 상태 /** * 🚫 2025-11-27: auth/check API 호출 제거 * * [이전 동작] * - 로그인 페이지 진입 시 /api/auth/check 호출 * - 이미 로그인된 사용자를 대시보드로 리다이렉트 * * [제거 이유] * 1. 미들웨어(middleware.ts)에서 이미 동일한 처리를 함 * - guestOnlyRoutes(/login, /signup)에서 인증된 사용자 → /dashboard 리다이렉트 * 2. 401 응답이 Network 탭에 에러로 표시되어 백엔드 개발자 혼란 유발 * 3. 불필요한 API 호출로 인한 성능 저하 * * [대체 방안] * - 미들웨어가 서버 사이드에서 쿠키 체크 후 리다이렉트 처리 * - 클라이언트에서 추가 API 호출 불필요 * * @see middleware.ts - isGuestOnlyRoute(), checkAuthentication() */ // useEffect(() => { // const checkAuth = async () => { // try { // const response = await fetch('/api/auth/check'); // if (response.ok) { // router.replace('/dashboard'); // return; // } // } catch { // // API 호출 실패 → 현재 페이지 유지 // } finally { // setIsChecking(false); // } // }; // checkAuth(); // }, [router]); const handleLogin = async () => { // ✅ 중복 요청 방지 if (isLoggingIn) { console.warn('⚠️ 로그인 진행 중 - 중복 요청 차단'); return; } setError(""); // Validation if (!userId || !password) { setError(tValidation('required')); return; } setIsLoggingIn(true); // ✅ 로그인 시작 try { // 🔵 Next.js 프록시 → PHP /api/v1/login (토큰을 HttpOnly 쿠키로 저장) const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ user_id: userId, user_pwd: password, }), }); const data = await response.json(); if (!response.ok) { throw { status: response.status, message: data.error || 'Login failed', }; } // API 메뉴를 MenuItem 구조로 변환 const transformedMenus = transformApiMenusToMenuItems(data.menus || []); // 서버에서 받은 사용자 정보를 localStorage에 저장 (대시보드에서 사용) const userData = { id: data.user?.id, // 실제 DB user ID (숫자) name: data.user?.name || userId, position: data.roles?.[0]?.description || '사용자', userId: userId, department: data.user?.department || null, department_id: data.user?.department_id || null, menu: transformedMenus, // 변환된 메뉴 구조 저장 roles: data.roles || [], tenant: data.tenant || {}, }; localStorage.setItem('user', JSON.stringify(userData)); // 유저별 persist store를 새 유저 키로 rehydrate const { useFavoritesStore } = await import('@/stores/favoritesStore'); const { useTableColumnStore } = await import('@/stores/useTableColumnStore'); useFavoritesStore.persist.rehydrate(); useTableColumnStore.persist.rehydrate(); // 메뉴 폴링 재시작 플래그 설정 (세션 만료 후 재로그인 시) sessionStorage.setItem('auth_just_logged_in', 'true'); // 대시보드로 이동 router.push("/dashboard"); } catch (err) { if (isNextRedirectError(err)) throw err; // 상세 에러 로깅 console.error('❌ 로그인 실패:', err); if (err instanceof Error) { console.error(' - Error name:', err.name); console.error(' - Error message:', err.message); console.error(' - Error stack:', err.stack); } else { console.error(' - Error details:', JSON.stringify(err, null, 2)); } const error = err as { status?: number; message?: string }; if (error.status === 401 || error.status === 422) { setError(t('invalidCredentials')); } else if (error.status === 429) { setError('Too many login attempts. Please try again later.'); } else if (error.status && error.status >= 500) { setError('Service temporarily unavailable. Please try again later.'); } else { setError(error.message || t('invalidCredentials')); } setIsLoggingIn(false); // ✅ 실패 시에만 버튼 재활성화 (성공 시 페이지 전환까지 비활성화 유지) } }; // 2025-11-27: isChecking 로딩 UI 제거 - 미들웨어에서 처리하므로 불필요 // if (isChecking) { // return ( //
Loading...
//{tCommon('welcome')} SAM ERP/MES
{error}