최초커밋
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
"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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user