"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { LoginFormData, LoginResponse } from "@/types/auth"; import { AUTH_CONFIG, FORM_VALIDATION } from "@/constants/auth"; import { API_BASE_URL } from "@/lib/api/client"; /** * 로그인 관련 비즈니스 로직을 관리하는 커스텀 훅 */ 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 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]); /** * API 호출 공통 함수 */ const apiCall = useCallback(async (endpoint: string, options: RequestInit = {}): Promise => { // 로컬 스토리지에서 토큰 가져오기 const token = localStorage.getItem("authToken"); const response = await fetch(`${API_BASE_URL}${endpoint}`, { credentials: "include", headers: { "Content-Type": "application/json", Accept: "application/json", ...(token && { Authorization: `Bearer ${token}` }), ...options.headers, }, ...options, }); const result = await response.json(); return result; }, []); /** * 기존 인증 상태 확인 */ const checkExistingAuth = useCallback(async () => { try { // 로컬 스토리지에서 토큰 확인 const token = localStorage.getItem("authToken"); if (!token) { // 토큰이 없으면 로그인 페이지 유지 return; } // 토큰이 있으면 API 호출로 유효성 확인 const result = await apiCall(AUTH_CONFIG.ENDPOINTS.STATUS); // 백엔드가 isAuthenticated 필드를 반환함 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 (error) { // 에러가 발생하면 토큰 제거 localStorage.removeItem("authToken"); document.cookie = "authToken=; path=/; max-age=0; SameSite=Lax"; console.debug("기존 인증 체크 중 오류 (정상):", error); } }, [apiCall, router]); /** * 로그인 처리 */ 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(AUTH_CONFIG.ENDPOINTS.LOGIN, { method: "POST", body: JSON.stringify(formData), }); 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`; // 로그인 성공 - 첫 번째 접근 가능한 메뉴로 리다이렉트 const firstMenuPath = result.data?.firstMenuPath; if (firstMenuPath) { // 접근 가능한 메뉴가 있으면 해당 메뉴로 이동 console.log("첫 번째 접근 가능한 메뉴로 이동:", firstMenuPath); router.push(firstMenuPath); } else { // 접근 가능한 메뉴가 없으면 메인 페이지로 이동 console.log("접근 가능한 메뉴가 없어 메인 페이지로 이동"); 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, apiCall, router], ); // 컴포넌트 마운트 시 기존 인증 상태 확인 useEffect(() => { checkExistingAuth(); }, [checkExistingAuth]); return { // 상태 formData, isLoading, error, showPassword, // 액션 handleInputChange, handleLogin, togglePasswordVisibility, }; };