diff --git a/src/app/error.tsx b/src/app/error.tsx new file mode 100644 index 0000000..ddd9385 --- /dev/null +++ b/src/app/error.tsx @@ -0,0 +1,22 @@ +"use client"; + +import Image from "next/image"; + +export default function NotFound() { + return ( +
+
+
+
+ Logo + GrowFolio +

서비스 업데이트 준비중입니다

+

+ 빠른 시일 내에 찾아뵙겠습니다 +

+
+
+
+
+ ); +} diff --git a/src/hooks/use-auth.ts b/src/hooks/use-auth.ts index 496ed83..03d4ba6 100644 --- a/src/hooks/use-auth.ts +++ b/src/hooks/use-auth.ts @@ -1,9 +1,7 @@ -/* eslint-disable*/ - +/*eslint-disable*/ "use client"; import { create } from "zustand"; - import { deleteCookie, getCookie, setCookie } from "@/utils/next-cookies"; interface LoginResponse { @@ -24,51 +22,59 @@ interface AuthStore { deposit: number | null; isAuthenticated: boolean; isInitialized: boolean; + isInitializing: boolean; setAuth: (response: LoginResponse) => Promise; clearAuth: () => Promise; initAuth: () => Promise; } /** - * 인증 토큰을 관리하는 Zustand 스토어 훅 + * 인증 상태를 관리하는 Zustand 스토어 훅 * - * @example - * // 1. 컴포넌트에서 토큰 관리 - * import { useAuth } from '@/hooks/useAuth'; + * @description + * - 로그인/로그아웃 상태 관리 + * - 토큰 및 사용자 정보 관리 + * - 쿠키 기반의 인증 상태 유지 + * - 중복 초기화 방지 로직 포함 * - * function LoginComponent() { + * @example + * // 1. 로그인 처리 + * const LoginComponent = () => { * const { setAuth } = useAuth(); * * const handleLogin = async () => { - * const response = await fetch('/api/login'); - * const data = await response.json(); - * await setAuth(data); // 로그인 후 인증 정보 저장 + * const response = await loginAPI(); + * await setAuth(response); * }; - * } + * }; * * @example - * // 2. API 요청시 토큰 사용 - * function ProtectedComponent() { - * const { token } = useAuth(); + * // 2. 인증이 필요한 API 요청 + * const ProtectedComponent = () => { + * const { token, isInitialized } = useAuth(); * - * const fetchData = async () => { - * const response = await fetch('/api/protected', { - * headers: { - * 'Authorization': `Bearer ${token}` - * } - * }); - * }; - * } + * useEffect(() => { + * if (!isInitialized) return; + * + * const fetchData = async () => { + * const response = await fetch('/api/protected', { + * headers: { Authorization: `Bearer ${token}` } + * }); + * }; + * + * fetchData(); + * }, [isInitialized, token]); + * }; * * @example - * // 3. 컴포넌트 마운트시 인증 상태 초기화 - * function App() { + * // 3. 앱 초기화시 인증 상태 복원 + * const App = () => { * const { initAuth } = useAuth(); * * useEffect(() => { - * initAuth(); // 페이지 로드시 쿠키에서 인증 정보 복원 + * initAuth(); * }, []); - * } + * }; * * @example * // 4. 로그아웃시 인증 정보 제거 @@ -76,89 +82,44 @@ interface AuthStore { * const { clearAuth } = useAuth(); * * const handleLogout = async () => { - * await clearAuth(); // 로그아웃시 인증 정보 삭제 + * await clearAuth(); * }; * } */ -export const useAuth = create((set) => ({ - token: null, - memberId: null, - memberName: null, - memberNickName: null, - annualIncome: null, - deposit: null, - isAuthenticated: false, - isInitialized: false, - - setAuth: async (response: LoginResponse) => { - const { - token, - memberId, - memberName, - memberNickName, - annualIncome, - deposit, - } = response; - - // Store all relevant data in cookies - await setCookie("token", token); - await setCookie("memberId", memberId.toString()); - await setCookie("memberName", memberName); - await setCookie("memberNickName", memberNickName); - await setCookie("annualIncome", annualIncome.toString()); - await setCookie("deposit", deposit.toString()); - - set({ - token, - memberId, - memberName, - memberNickName, - annualIncome, - deposit, - isAuthenticated: true, - isInitialized: true, - }); - }, - - clearAuth: async () => { - // Clear all cookies - await deleteCookie("token"); - await deleteCookie("memberId"); - await deleteCookie("memberName"); - await deleteCookie("memberNickName"); - await deleteCookie("annualIncome"); - await deleteCookie("deposit"); - - set({ - token: null, - memberId: null, - memberName: null, - memberNickName: null, - annualIncome: null, - deposit: null, - isAuthenticated: false, - isInitialized: true, - }); - }, - - initAuth: async () => { - try { - set({ isInitialized: false }); - - const token = await getCookie("token"); - const memberIdStr = await getCookie("memberId"); - const memberName = await getCookie("memberName"); - const memberNickName = await getCookie("memberNickName"); - const annualIncomeStr = await getCookie("annualIncome"); - const depositStr = await getCookie("deposit"); - - // Convert string values back to numbers - const memberId = memberIdStr ? parseInt(memberIdStr, 10) : null; - const annualIncome = annualIncomeStr - ? parseInt(annualIncomeStr, 10) - : null; - const deposit = depositStr ? parseInt(depositStr, 10) : null; +export const useAuth = create((set, get) => { + let initPromise: Promise | null = null; + + return { + token: null, + memberId: null, + memberName: null, + memberNickName: null, + annualIncome: null, + deposit: null, + isAuthenticated: false, + isInitialized: false, + isInitializing: false, + + setAuth: async (response: LoginResponse) => { + const { + token, + memberId, + memberName, + memberNickName, + annualIncome, + deposit, + } = response; + + // 모든 쿠키 설정을 병렬로 처리 + await Promise.all([ + setCookie("token", token), + setCookie("memberId", memberId.toString()), + setCookie("memberName", memberName), + setCookie("memberNickName", memberNickName), + setCookie("annualIncome", annualIncome.toString()), + setCookie("deposit", deposit.toString()), + ]); set({ token, @@ -167,12 +128,93 @@ export const useAuth = create((set) => ({ memberNickName, annualIncome, deposit, - isAuthenticated: !!token, + isAuthenticated: true, + isInitialized: true, + }); + }, + + clearAuth: async () => { + // 모든 쿠키 삭제를 병렬로 처리 + await Promise.all([ + deleteCookie("token"), + deleteCookie("memberId"), + deleteCookie("memberName"), + deleteCookie("memberNickName"), + deleteCookie("annualIncome"), + deleteCookie("deposit"), + ]); + + set({ + token: null, + memberId: null, + memberName: null, + memberNickName: null, + annualIncome: null, + deposit: null, + isAuthenticated: false, isInitialized: true, }); - } catch (error) { - set({ isInitialized: true }); - throw error; - } - }, -})); + }, + + initAuth: async () => { + if (get().isInitialized) { + return; + } + + if (initPromise) { + return initPromise; + } + + initPromise = (async () => { + try { + set({ isInitializing: true }); + + // 모든 쿠키 조회를 병렬로 처리 + const [ + token, + memberIdStr, + memberName, + memberNickName, + annualIncomeStr, + depositStr, + ] = await Promise.all([ + getCookie("token"), + getCookie("memberId"), + getCookie("memberName"), + getCookie("memberNickName"), + getCookie("annualIncome"), + getCookie("deposit"), + ]); + + const memberId = memberIdStr ? parseInt(memberIdStr, 10) : null; + const annualIncome = annualIncomeStr + ? parseInt(annualIncomeStr, 10) + : null; + const deposit = depositStr ? parseInt(depositStr, 10) : null; + + set({ + token, + memberId, + memberName, + memberNickName, + annualIncome, + deposit, + isAuthenticated: !!token, + isInitialized: true, + isInitializing: false, + }); + } catch (error) { + set({ + isInitialized: true, + isInitializing: false, + }); + throw error; + } finally { + initPromise = null; + } + })(); + + return initPromise; + }, + }; +});