"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; 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"; export function LoginPage() { const router = useRouter(); const t = useTranslations('auth'); const tCommon = useTranslations('common'); const tValidation = useTranslations('validation'); const [userId, setUserId] = useState(""); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [rememberMe, setRememberMe] = useState(false); const [error, setError] = useState(""); const [isChecking, setIsChecking] = useState(true); // 이미 로그인된 상태인지 확인 (페이지 로드 시, 뒤로가기 시) useEffect(() => { const checkAuth = async () => { try { // 🔵 Next.js 내부 API - 쿠키에서 토큰 확인 (PHP 호출 X, 성능 최적화) const response = await fetch('/api/auth/check'); if (response.ok) { // 이미 로그인됨 → 대시보드로 리다이렉트 (replace로 히스토리에서 제거) router.replace('/dashboard'); return; } // 인증 안됨 (401) → 현재 페이지 유지 } catch { // API 호출 실패 → 현재 페이지 유지 } finally { setIsChecking(false); } }; checkAuth(); }, [router]); const handleLogin = async () => { setError(""); // Validation if (!userId || !password) { setError(tValidation('required')); return; } 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', }; } console.log('✅ 로그인 성공:', data.message); console.log('📦 사용자 정보:', data.user); console.log('📋 메뉴 정보 (API):', data.menus); console.log('👥 역할 정보:', data.roles); console.log('🏢 테넌트 정보:', data.tenant); console.log('🔐 토큰은 안전한 HttpOnly 쿠키에 저장됨 (JavaScript 접근 불가)'); // API 메뉴를 MenuItem 구조로 변환 const transformedMenus = transformApiMenusToMenuItems(data.menus || []); console.log('🔄 변환된 메뉴 구조:', transformedMenus); // 서버에서 받은 사용자 정보를 localStorage에 저장 (대시보드에서 사용) const userData = { name: data.user?.name || userId, position: data.roles?.[0]?.description || '사용자', userId: userId, menu: transformedMenus, // 변환된 메뉴 구조 저장 roles: data.roles || [], tenant: data.tenant || {}, }; console.log('💾 localStorage에 저장할 데이터:', userData); localStorage.setItem('user', JSON.stringify(userData)); // 대시보드로 이동 router.push("/dashboard"); } catch (err) { console.error('❌ 로그인 실패:', err); 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')); } } }; // 인증 체크 중일 때는 로딩 표시 if (isChecking) { return (

Loading...

); } return (
{/* Header */}
{/* Main Content */}
{/* Login Card */}

{t('login')}

{tCommon('welcome')} SAM MES

{error && (

{error}

)}
{ e.preventDefault(); handleLogin(); }} className="space-y-4">
setUserId(e.target.value)} className="clean-input" />
setPassword(e.target.value)} className="clean-input pr-10" />
{tCommon('or')}
{/* Signup Link */}

{t('noAccount')}{" "}

); }