From 1c7931c165dd6ecc7e68bcafe1d25012e38755b6 Mon Sep 17 00:00:00 2001 From: Han-wo Date: Thu, 26 Dec 2024 19:51:12 +0900 Subject: [PATCH 1/4] =?UTF-8?q?merge:=2086=20=EB=B8=8C=EB=9E=9C=EC=B9=98?= =?UTF-8?q?=EC=99=80=20=EB=A8=B8=EC=A7=80=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/login/page.tsx | 7 ++----- src/app/members/page.tsx | 5 +---- src/components/common/auth/logo.tsx | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index d091a76..9d1b29a 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,4 +1,4 @@ -import Logo from "@/components/common/auth/logo"; +import LoginHeader from "@/components/common/auth/logo"; import LoginForm from "./login-form"; @@ -6,10 +6,7 @@ export default function LoginPage() { return (
-
- -

로그인후 서비스를 이용해주세요

-
+
diff --git a/src/app/members/page.tsx b/src/app/members/page.tsx index 89390f8..da99a72 100644 --- a/src/app/members/page.tsx +++ b/src/app/members/page.tsx @@ -6,10 +6,7 @@ export default function SignUpPage() { return (
-
- -

회원가입후 서비스를 이용해주세요

-
+
diff --git a/src/components/common/auth/logo.tsx b/src/components/common/auth/logo.tsx index d4844e6..7974403 100644 --- a/src/components/common/auth/logo.tsx +++ b/src/components/common/auth/logo.tsx @@ -1,7 +1,10 @@ +"use client"; + import Image from "next/image"; import Link from "next/link"; +import { memo } from "react"; -export default function Logo() { +function Logo() { return (
@@ -11,3 +14,14 @@ export default function Logo() { ); } + +function LoginHeader() { + return ( +
+ +

로그인후 서비스를 이용해주세요

+
+ ); +} + +export default memo(LoginHeader); From ccabaab338ce367de4302aabf6d4a6c3c85d9fe4 Mon Sep 17 00:00:00 2001 From: Han-wo Date: Fri, 27 Dec 2024 17:48:34 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20useAuth=EC=97=90=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=B0=A9=EC=A7=80?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/use-auth.ts | 284 +++++++++++++++++++++++++----------------- 1 file changed, 171 insertions(+), 113 deletions(-) diff --git a/src/hooks/use-auth.ts b/src/hooks/use-auth.ts index 496ed83..44d7cb3 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,52 @@ 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, + + /** + * 로그인 응답을 받아 인증 상태를 설정하는 함수 + * + * @param response - 로그인 API 응답 데이터 + * @returns Promise + * + * @description + * - 토큰과 사용자 정보를 쿠키에 저장 + * - 스토어의 상태를 업데이트 + * - 인증 상태를 true로 설정 + */ + setAuth: async (response: LoginResponse) => { + const { + token, + memberId, + memberName, + memberNickName, + annualIncome, + deposit, + } = response; + + 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, @@ -167,12 +136,101 @@ export const useAuth = create((set) => ({ memberNickName, annualIncome, deposit, - isAuthenticated: !!token, + isAuthenticated: true, + isInitialized: true, + }); + }, + + /** + * 인증 상태를 초기화하고 로그아웃하는 함수 + * + * @returns Promise + * + * @description + * - 모든 인증 관련 쿠키를 삭제 + * - 스토어의 상태를 초기화 + * - 인증 상태를 false로 설정 + */ + clearAuth: async () => { + 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, }); - } catch (error) { - set({ isInitialized: true }); - throw error; - } - }, -})); + }, + + /** + * 페이지 로드시 쿠키에서 인증 상태를 복원하는 함수 + * + * @returns Promise + * + * @description + * - 중복 초기화 방지를 위한 Promise 캐싱 포함 + * - 쿠키에서 인증 정보를 읽어와 스토어에 설정 + * - 초기화 상태를 추적하기 위한 플래그 관리 + * - 에러 발생시 초기화 상태 정리 + */ + initAuth: async () => { + if (get().isInitialized) { + return; + } + + if (initPromise) { + return initPromise; + } + + initPromise = (async () => { + try { + set({ isInitializing: true }); + + 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"); + + 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; + }, + }; +}); From 1ae3765124dfa744b4e9849de8192af53321698f Mon Sep 17 00:00:00 2001 From: Han-wo Date: Fri, 27 Dec 2024 18:16:27 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=B9=84=EB=8F=99=EA=B8=B0=20=EB=B3=91=EB=A0=AC?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/use-auth.ts | 84 ++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/src/hooks/use-auth.ts b/src/hooks/use-auth.ts index 44d7cb3..03d4ba6 100644 --- a/src/hooks/use-auth.ts +++ b/src/hooks/use-auth.ts @@ -101,17 +101,6 @@ export const useAuth = create((set, get) => { isInitialized: false, isInitializing: false, - /** - * 로그인 응답을 받아 인증 상태를 설정하는 함수 - * - * @param response - 로그인 API 응답 데이터 - * @returns Promise - * - * @description - * - 토큰과 사용자 정보를 쿠키에 저장 - * - 스토어의 상태를 업데이트 - * - 인증 상태를 true로 설정 - */ setAuth: async (response: LoginResponse) => { const { token, @@ -122,12 +111,15 @@ export const useAuth = create((set, get) => { deposit, } = response; - 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()); + // 모든 쿠키 설정을 병렬로 처리 + 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, @@ -141,23 +133,16 @@ export const useAuth = create((set, get) => { }); }, - /** - * 인증 상태를 초기화하고 로그아웃하는 함수 - * - * @returns Promise - * - * @description - * - 모든 인증 관련 쿠키를 삭제 - * - 스토어의 상태를 초기화 - * - 인증 상태를 false로 설정 - */ clearAuth: async () => { - await deleteCookie("token"); - await deleteCookie("memberId"); - await deleteCookie("memberName"); - await deleteCookie("memberNickName"); - await deleteCookie("annualIncome"); - await deleteCookie("deposit"); + // 모든 쿠키 삭제를 병렬로 처리 + await Promise.all([ + deleteCookie("token"), + deleteCookie("memberId"), + deleteCookie("memberName"), + deleteCookie("memberNickName"), + deleteCookie("annualIncome"), + deleteCookie("deposit"), + ]); set({ token: null, @@ -171,17 +156,6 @@ export const useAuth = create((set, get) => { }); }, - /** - * 페이지 로드시 쿠키에서 인증 상태를 복원하는 함수 - * - * @returns Promise - * - * @description - * - 중복 초기화 방지를 위한 Promise 캐싱 포함 - * - 쿠키에서 인증 정보를 읽어와 스토어에 설정 - * - 초기화 상태를 추적하기 위한 플래그 관리 - * - 에러 발생시 초기화 상태 정리 - */ initAuth: async () => { if (get().isInitialized) { return; @@ -195,12 +169,22 @@ export const useAuth = create((set, get) => { try { set({ isInitializing: true }); - 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"); + // 모든 쿠키 조회를 병렬로 처리 + 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 From e56cbd072dc406e7a974f90c50bf0693e839b1cf Mon Sep 17 00:00:00 2001 From: Han-wo Date: Wed, 1 Jan 2025 14:22:01 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A0=9C=EC=9E=91=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/error.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/app/error.tsx 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 +

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

+

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

+
+
+
+
+ ); +}