"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { LoginFormData } from "@/types/auth"; import { AUTH_CONFIG, FORM_VALIDATION } from "@/constants/auth"; import { apiCall } from "@/lib/api/client"; /** * 로그인 관련 비즈니스 로직을 관리하는 커스텀 훅 * API 호출은 lib/api/client의 apiCall(Axios) 사용 (fetch 직접 사용 금지) */ export const useLogin = () => { const router = useRouter(); // 상태 관리 const [formData, setFormData] = useState({ userId: "", password: "", }); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(""); const [showPassword, setShowPassword] = useState(false); const [isPopMode, setIsPopMode] = useState(false); // localStorage에서 POP 모드 상태 복원 useEffect(() => { const saved = localStorage.getItem("popLoginMode"); if (saved === "true") setIsPopMode(true); }, []); const togglePopMode = useCallback(() => { setIsPopMode((prev) => { const next = !prev; localStorage.setItem("popLoginMode", String(next)); return next; }); }, []); /** * 폼 입력값 변경 처리 */ const handleInputChange = useCallback( (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value, })); // 입력 시 에러 메시지 제거 if (error) setError(""); }, [error], ); /** * 비밀번호 표시/숨김 토글 */ const togglePasswordVisibility = useCallback(() => { setShowPassword((prev) => !prev); }, []); /** * 입력값 검증 */ const validateForm = useCallback((): string | null => { if (!formData.userId.trim()) { return FORM_VALIDATION.MESSAGES.USER_ID_REQUIRED; } if (!formData.password.trim()) { return FORM_VALIDATION.MESSAGES.PASSWORD_REQUIRED; } return null; }, [formData]); /** * 기존 인증 상태 확인 (apiCall 사용) */ const checkExistingAuth = useCallback(async () => { try { const token = localStorage.getItem("authToken"); if (!token) return; const result = await apiCall<{ isAuthenticated?: boolean }>("GET", AUTH_CONFIG.ENDPOINTS.STATUS); if (result.success && result.data?.isAuthenticated) { router.push(AUTH_CONFIG.ROUTES.MAIN); } else { localStorage.removeItem("authToken"); document.cookie = "authToken=; path=/; max-age=0; SameSite=Lax"; } } catch { localStorage.removeItem("authToken"); document.cookie = "authToken=; path=/; max-age=0; SameSite=Lax"; } }, [router]); /** * 로그인 처리 (apiCall 사용 - Axios 기반, fetch 미사용) */ const handleLogin = useCallback( async (e: React.FormEvent) => { e.preventDefault(); const validationError = validateForm(); if (validationError) { setError(validationError); return; } setIsLoading(true); setError(""); try { const result = await apiCall<{ token?: string; firstMenuPath?: string; popLandingPath?: string; }>("POST", AUTH_CONFIG.ENDPOINTS.LOGIN, { userId: formData.userId, password: formData.password, }); if (result.success && result.data?.token) { // JWT 토큰 저장 (localStorage와 쿠키 모두에 저장) localStorage.setItem("authToken", result.data.token); // 쿠키에도 저장 (미들웨어에서 사용) document.cookie = `authToken=${result.data.token}; path=/; max-age=86400; SameSite=Lax`; if (isPopMode) { const popPath = result.data?.popLandingPath; if (popPath) { router.push(popPath); } else { setError("POP 화면이 설정되어 있지 않습니다. 관리자에게 메뉴 관리에서 POP 화면을 설정해달라고 요청하세요."); setIsLoading(false); return; } } else { const firstMenuPath = result.data?.firstMenuPath; if (firstMenuPath) { router.push(firstMenuPath); } else { router.push(AUTH_CONFIG.ROUTES.MAIN); } } } else { // 로그인 실패 setError(result.message || FORM_VALIDATION.MESSAGES.LOGIN_FAILED); console.error("로그인 실패:", result); } } catch (error) { console.error("로그인 오류:", error); setError(FORM_VALIDATION.MESSAGES.CONNECTION_FAILED); } finally { setIsLoading(false); } }, [formData, validateForm, router, isPopMode], ); // 컴포넌트 마운트 시 기존 인증 상태 확인 useEffect(() => { checkExistingAuth(); }, [checkExistingAuth]); return { // 상태 formData, isLoading, error, showPassword, isPopMode, // 액션 handleInputChange, handleLogin, togglePasswordVisibility, togglePopMode, }; };