2025-08-21 09:41:46 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect, useCallback } from "react";
|
|
|
|
|
import { useRouter } from "next/navigation";
|
2026-03-17 18:25:36 +09:00
|
|
|
import { LoginFormData } from "@/types/auth";
|
2025-08-21 09:41:46 +09:00
|
|
|
import { AUTH_CONFIG, FORM_VALIDATION } from "@/constants/auth";
|
2026-03-17 18:25:36 +09:00
|
|
|
import { apiCall } from "@/lib/api/client";
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 로그인 관련 비즈니스 로직을 관리하는 커스텀 훅
|
2026-03-17 18:25:36 +09:00
|
|
|
* API 호출은 lib/api/client의 apiCall(Axios) 사용 (fetch 직접 사용 금지)
|
2025-08-21 09:41:46 +09:00
|
|
|
*/
|
|
|
|
|
export const useLogin = () => {
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
// 상태 관리
|
|
|
|
|
const [formData, setFormData] = useState<LoginFormData>({
|
|
|
|
|
userId: "",
|
|
|
|
|
password: "",
|
|
|
|
|
});
|
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
|
const [error, setError] = useState("");
|
|
|
|
|
const [showPassword, setShowPassword] = useState(false);
|
2026-03-09 15:15:15 +09:00
|
|
|
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;
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 폼 입력값 변경 처리
|
|
|
|
|
*/
|
|
|
|
|
const handleInputChange = useCallback(
|
|
|
|
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
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]);
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-17 18:25:36 +09:00
|
|
|
* 기존 인증 상태 확인 (apiCall 사용)
|
2025-08-21 09:41:46 +09:00
|
|
|
*/
|
|
|
|
|
const checkExistingAuth = useCallback(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const token = localStorage.getItem("authToken");
|
2026-03-17 18:25:36 +09:00
|
|
|
if (!token) return;
|
2025-08-21 09:41:46 +09:00
|
|
|
|
2026-03-17 18:25:36 +09:00
|
|
|
const result = await apiCall<{ isAuthenticated?: boolean }>("GET", AUTH_CONFIG.ENDPOINTS.STATUS);
|
2025-08-21 09:41:46 +09:00
|
|
|
|
2025-10-24 10:09:19 +09:00
|
|
|
if (result.success && result.data?.isAuthenticated) {
|
2025-08-21 09:41:46 +09:00
|
|
|
router.push(AUTH_CONFIG.ROUTES.MAIN);
|
|
|
|
|
} else {
|
|
|
|
|
localStorage.removeItem("authToken");
|
2025-10-24 10:09:19 +09:00
|
|
|
document.cookie = "authToken=; path=/; max-age=0; SameSite=Lax";
|
2025-08-21 09:41:46 +09:00
|
|
|
}
|
2026-03-17 18:25:36 +09:00
|
|
|
} catch {
|
2025-08-21 09:41:46 +09:00
|
|
|
localStorage.removeItem("authToken");
|
2025-10-24 10:09:19 +09:00
|
|
|
document.cookie = "authToken=; path=/; max-age=0; SameSite=Lax";
|
2025-08-21 09:41:46 +09:00
|
|
|
}
|
2026-03-17 18:25:36 +09:00
|
|
|
}, [router]);
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
/**
|
2026-03-17 18:25:36 +09:00
|
|
|
* 로그인 처리 (apiCall 사용 - Axios 기반, fetch 미사용)
|
2025-08-21 09:41:46 +09:00
|
|
|
*/
|
|
|
|
|
const handleLogin = useCallback(
|
|
|
|
|
async (e: React.FormEvent) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
const validationError = validateForm();
|
|
|
|
|
if (validationError) {
|
|
|
|
|
setError(validationError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
setError("");
|
|
|
|
|
|
|
|
|
|
try {
|
2026-03-17 18:25:36 +09:00
|
|
|
const result = await apiCall<{
|
|
|
|
|
token?: string;
|
|
|
|
|
firstMenuPath?: string;
|
|
|
|
|
popLandingPath?: string;
|
|
|
|
|
}>("POST", AUTH_CONFIG.ENDPOINTS.LOGIN, {
|
|
|
|
|
userId: formData.userId,
|
|
|
|
|
password: formData.password,
|
2025-08-21 09:41:46 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.success && result.data?.token) {
|
2025-10-24 10:09:19 +09:00
|
|
|
// JWT 토큰 저장 (localStorage와 쿠키 모두에 저장)
|
2025-08-21 09:41:46 +09:00
|
|
|
localStorage.setItem("authToken", result.data.token);
|
|
|
|
|
|
2025-10-24 10:09:19 +09:00
|
|
|
// 쿠키에도 저장 (미들웨어에서 사용)
|
|
|
|
|
document.cookie = `authToken=${result.data.token}; path=/; max-age=86400; SameSite=Lax`;
|
|
|
|
|
|
2026-03-09 15:15:15 +09:00
|
|
|
if (isPopMode) {
|
|
|
|
|
const popPath = result.data?.popLandingPath;
|
|
|
|
|
if (popPath) {
|
|
|
|
|
router.push(popPath);
|
|
|
|
|
} else {
|
|
|
|
|
setError("POP 화면이 설정되어 있지 않습니다. 관리자에게 메뉴 관리에서 POP 화면을 설정해달라고 요청하세요.");
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-10-28 14:55:41 +09:00
|
|
|
} else {
|
2026-03-09 15:15:15 +09:00
|
|
|
const firstMenuPath = result.data?.firstMenuPath;
|
|
|
|
|
if (firstMenuPath) {
|
|
|
|
|
router.push(firstMenuPath);
|
|
|
|
|
} else {
|
|
|
|
|
router.push(AUTH_CONFIG.ROUTES.MAIN);
|
|
|
|
|
}
|
2025-10-28 14:55:41 +09:00
|
|
|
}
|
2025-08-21 09:41:46 +09:00
|
|
|
} 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);
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-03-17 18:25:36 +09:00
|
|
|
[formData, validateForm, router, isPopMode],
|
2025-08-21 09:41:46 +09:00
|
|
|
);
|
|
|
|
|
|
2025-10-24 10:09:19 +09:00
|
|
|
// 컴포넌트 마운트 시 기존 인증 상태 확인
|
2025-08-21 09:41:46 +09:00
|
|
|
useEffect(() => {
|
2025-10-24 10:09:19 +09:00
|
|
|
checkExistingAuth();
|
|
|
|
|
}, [checkExistingAuth]);
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
// 상태
|
|
|
|
|
formData,
|
|
|
|
|
isLoading,
|
|
|
|
|
error,
|
|
|
|
|
showPassword,
|
2026-03-09 15:15:15 +09:00
|
|
|
isPopMode,
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
// 액션
|
|
|
|
|
handleInputChange,
|
|
|
|
|
handleLogin,
|
|
|
|
|
togglePasswordVisibility,
|
2026-03-09 15:15:15 +09:00
|
|
|
togglePopMode,
|
2025-08-21 09:41:46 +09:00
|
|
|
};
|
|
|
|
|
};
|