ERP-node/frontend/hooks/useLogin.ts

172 lines
4.6 KiB
TypeScript
Raw Normal View History

2025-08-21 09:41:46 +09:00
"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";
/**
*
*/
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);
/**
*
*/
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]);
/**
* API
*/
const apiCall = useCallback(async (endpoint: string, options: RequestInit = {}): Promise<LoginResponse> => {
const response = await fetch(`${AUTH_CONFIG.API_BASE_URL}${endpoint}`, {
credentials: "include",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
...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);
if (result.success && result.data?.isLoggedIn) {
// 이미 로그인된 경우 메인으로 리다이렉트
router.push(AUTH_CONFIG.ROUTES.MAIN);
} else {
// 토큰이 유효하지 않으면 제거
localStorage.removeItem("authToken");
}
} catch (error) {
// 에러가 발생하면 토큰 제거
localStorage.removeItem("authToken");
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.setItem("authToken", result.data.token);
// 로그인 성공
console.log("로그인 성공:", result.message || "로그인에 성공했습니다.");
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(() => {
// 로그인 페이지에서만 실행
if (window.location.pathname === "/login") {
checkExistingAuth();
}
}, []); // 의존성 배열을 비워서 한 번만 실행
return {
// 상태
formData,
isLoading,
error,
showPassword,
// 액션
handleInputChange,
handleLogin,
togglePasswordVisibility,
};
};