From 9ec7412cf988c357cd0124aa10bc3f37803cea92 Mon Sep 17 00:00:00 2001 From: ahk0413 Date: Mon, 13 Oct 2025 10:05:49 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[fix]=20persist=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domains/shared/store/auth.ts | 95 +++++++++++++++----------------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/src/domains/shared/store/auth.ts b/src/domains/shared/store/auth.ts index 3a662b24..01a39e54 100644 --- a/src/domains/shared/store/auth.ts +++ b/src/domains/shared/store/auth.ts @@ -22,59 +22,54 @@ interface AuthState { updateUser: () => Promise; } -export const useAuthStore = create()( - persist( - (set) => ({ - user: null, - accessToken: null, - isLoggedIn: false, +export const useAuthStore = create()((set) => ({ + user: null, + accessToken: null, + isLoggedIn: false, - loginWithProvider: (provider) => { - window.location.href = `${getApi}/oauth2/authorization/${provider}`; - }, + loginWithProvider: (provider) => { + window.location.href = `${getApi}/oauth2/authorization/${provider}`; + }, - setUser: (user, token) => { - const updatedUser = { ...user, abv_degree: 5.0 }; - set({ user: updatedUser, accessToken: token, isLoggedIn: true }); - }, + setUser: (user, token) => { + const updatedUser = { ...user, abv_degree: 5.0 }; + set({ user: updatedUser, accessToken: token, isLoggedIn: true }); + }, - logout: async () => { - try { - await fetch(`${getApi}/user/auth/logout`, { - method: 'POST', - credentials: 'include', - }); - set({ user: null, accessToken: null, isLoggedIn: false }); - } catch (err) { - console.error('로그아웃 실패', err); - } - }, + logout: async () => { + try { + await fetch(`${getApi}/user/auth/logout`, { + method: 'POST', + credentials: 'include', + }); + set({ user: null, accessToken: null, isLoggedIn: false }); + } catch (err) { + console.error('로그아웃 실패', err); + } + }, - updateUser: async () => { - try { - const res = await fetch(`${getApi}/user/auth/refresh`, { - method: 'POST', - credentials: 'include', - headers: { 'Content-Type': 'application/json' }, - }); + updateUser: async () => { + try { + const res = await fetch(`${getApi}/user/auth/refresh`, { + method: 'POST', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + }); - if (!res.ok) throw new Error('토큰 갱신 실패'); - const data = await res.json(); - const userInfo = data?.data?.user; - const accessToken = data?.data?.accessToken; + if (!res.ok) throw new Error('토큰 갱신 실패'); + const data = await res.json(); + const userInfo = data?.data?.user; + const accessToken = data?.data?.accessToken; - if (userInfo && accessToken) { - set({ user: userInfo, accessToken, isLoggedIn: true }); - return userInfo; - } - return null; - } catch (err) { - console.error('updateUser 실패', err); - set({ accessToken: null, user: null, isLoggedIn: false }); - return null; - } - }, - }), - { name: 'auth-storage' } // localStorage key - ) -); + if (userInfo && accessToken) { + set({ user: userInfo, accessToken, isLoggedIn: true }); + return userInfo; + } + return null; + } catch (err) { + console.error('updateUser 실패', err); + set({ accessToken: null, user: null, isLoggedIn: false }); + return null; + } + }, +})); From 3cd9a51c19b5f0e3a437e4bc4f66af693373a538 Mon Sep 17 00:00:00 2001 From: ahk0413 Date: Mon, 13 Oct 2025 14:35:51 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[fix]=20=EC=BF=A0=ED=82=A4=EB=A1=9C=20user?= =?UTF-8?q?=20=EC=B2=B4=ED=81=AC=20=EB=8C=93=EA=B8=80=EC=AA=BD=EB=8F=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domains/community/api/fetchComment.ts | 13 ++--- src/domains/community/detail/Comment.tsx | 5 +- src/domains/community/hook/useComment.ts | 6 +-- .../login/components/ClientInitHook.tsx | 8 ++++ src/domains/recipe/api/fetchRecipeComment.ts | 13 ++--- src/domains/recipe/api/useRecipeComment.ts | 10 ++-- .../components/details/RecipeComment.tsx | 5 +- src/domains/shared/store/auth.ts | 48 ++++++++++++++----- 8 files changed, 60 insertions(+), 48 deletions(-) diff --git a/src/domains/community/api/fetchComment.ts b/src/domains/community/api/fetchComment.ts index 4d66342c..c4d1ffef 100644 --- a/src/domains/community/api/fetchComment.ts +++ b/src/domains/community/api/fetchComment.ts @@ -48,7 +48,6 @@ export const postComments = async (postId: number | ParamValue, content: string) }; export async function updateComment( - accessToken: string | null, postId: number | ParamValue, commentId: number, content: string @@ -57,8 +56,8 @@ export async function updateComment( method: 'PATCH', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken}`, }, + credentials: 'include', body: JSON.stringify({ content }), }); @@ -69,16 +68,10 @@ export async function updateComment( } } -export async function deleteComment( - accessToken: string | null, - postId: number | ParamValue, - commentId: number -): Promise { +export async function deleteComment(postId: number | ParamValue, commentId: number): Promise { const response = await fetch(`${getApi}/posts/${postId}/comments/${commentId}`, { method: 'DELETE', - headers: { - Authorization: `Bearer ${accessToken}`, - }, + credentials: 'include', }); if (!response.ok) { diff --git a/src/domains/community/detail/Comment.tsx b/src/domains/community/detail/Comment.tsx index f7009de8..ea93f631 100644 --- a/src/domains/community/detail/Comment.tsx +++ b/src/domains/community/detail/Comment.tsx @@ -12,10 +12,9 @@ type Props = { }; function Comment({ postId }: Props) { - const { user, accessToken } = useAuthStore( + const { user } = useAuthStore( useShallow((state) => ({ user: state.user, - accessToken: state.accessToken, })) ); const { @@ -29,7 +28,7 @@ function Comment({ postId }: Props) { handleAskDeleteComment, handleConfirmDelete, loadMoreComments, - } = useComments(postId, user, accessToken); + } = useComments(postId, user); return ( <> diff --git a/src/domains/community/hook/useComment.ts b/src/domains/community/hook/useComment.ts index d94e0f90..e7340020 100644 --- a/src/domains/community/hook/useComment.ts +++ b/src/domains/community/hook/useComment.ts @@ -5,7 +5,7 @@ import { CommentType } from '../types/post'; import { User } from '@/domains/shared/store/auth'; import { ParamValue } from 'next/dist/server/request/params'; -export function useComments(postId: ParamValue, user: User | null, accessToken?: string | null) { +export function useComments(postId: ParamValue, user: User | null) { const [comments, setComments] = useState(null); const [isEnd, setIsEnd] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -31,7 +31,7 @@ export function useComments(postId: ParamValue, user: User | null, accessToken?: return; } try { - await updateComment(accessToken!, postId, commentId, content); + await updateComment(postId, commentId, content); setComments((prev) => prev ? prev.map((comment) => @@ -59,7 +59,7 @@ export function useComments(postId: ParamValue, user: User | null, accessToken?: if (!deleteTarget) return; try { - await deleteComment(accessToken!, deleteTarget.postId, deleteTarget.commentId); + await deleteComment(deleteTarget.postId, deleteTarget.commentId); setComments((prev) => prev ? prev.filter((c) => c.commentId !== deleteTarget.commentId) : prev ); diff --git a/src/domains/login/components/ClientInitHook.tsx b/src/domains/login/components/ClientInitHook.tsx index a73a7ba9..034d2845 100644 --- a/src/domains/login/components/ClientInitHook.tsx +++ b/src/domains/login/components/ClientInitHook.tsx @@ -2,10 +2,18 @@ import { useFetchInterceptor } from '@/shared/hook/useFetchInterceptor'; import { useIdleLogout } from '../hook/useIdleLogout'; +import { useEffect } from 'react'; +import { useAuthStore } from '@/domains/shared/store/auth'; function ClientInitHook() { + const checkAuth = useAuthStore((state) => state.checkAuth); + useIdleLogout(); useFetchInterceptor(); + + useEffect(() => { + checkAuth(); + }, [checkAuth]); return null; } export default ClientInitHook; diff --git a/src/domains/recipe/api/fetchRecipeComment.ts b/src/domains/recipe/api/fetchRecipeComment.ts index 627001ee..80f758a4 100644 --- a/src/domains/recipe/api/fetchRecipeComment.ts +++ b/src/domains/recipe/api/fetchRecipeComment.ts @@ -22,7 +22,6 @@ export const getRecipeComment = async (cocktailId: number): Promise { +export async function deleteRecipeComment(cocktailId: number, commentId: number): Promise { const response = await fetch(`${getApi}/cocktails/${cocktailId}/comments/${commentId}`, { method: 'DELETE', - headers: { - Authorization: `Bearer ${accessToken}`, - }, + credentials: 'include', }); if (!response.ok) { diff --git a/src/domains/recipe/api/useRecipeComment.ts b/src/domains/recipe/api/useRecipeComment.ts index d5aa6f6d..3fa8e6d1 100644 --- a/src/domains/recipe/api/useRecipeComment.ts +++ b/src/domains/recipe/api/useRecipeComment.ts @@ -5,11 +5,7 @@ import { CommentType } from '@/domains/community/types/post'; import { deleteRecipeComment, getRecipeComment, updateComment } from './fetchRecipeComment'; import { useToast } from '@/shared/hook/useToast'; -export function useRecipeComments( - cocktailId: number, - user: User | null, - accessToken: string | null -) { +export function useRecipeComments(cocktailId: number, user: User | null) { const [comments, setComments] = useState(null); const [isEnd, setIsEnd] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -36,7 +32,7 @@ export function useRecipeComments( return; } try { - await updateComment(accessToken!, cocktailId, commentId, content); + await updateComment(cocktailId, commentId, content); setComments((prev) => prev ? prev.map((comment) => @@ -62,7 +58,7 @@ export function useRecipeComments( if (!deleteTarget) return; try { - await deleteRecipeComment(accessToken!, deleteTarget.cocktailId, deleteTarget.commentId); + await deleteRecipeComment(deleteTarget.cocktailId, deleteTarget.commentId); setComments((prev) => prev ? prev.filter((c) => c.commentId !== deleteTarget.commentId) : prev ); diff --git a/src/domains/recipe/components/details/RecipeComment.tsx b/src/domains/recipe/components/details/RecipeComment.tsx index e85e6ec8..581a0608 100644 --- a/src/domains/recipe/components/details/RecipeComment.tsx +++ b/src/domains/recipe/components/details/RecipeComment.tsx @@ -13,10 +13,9 @@ interface Props { } function RecipeComment({ cocktailId }: Props) { - const { user, accessToken } = useAuthStore( + const { user } = useAuthStore( useShallow((state) => ({ user: state.user, - accessToken: state.accessToken, })) ); @@ -60,7 +59,7 @@ function RecipeComment({ cocktailId }: Props) { deleteTarget, handleConfirmDelete, setDeleteTarget, - } = useRecipeComments(cocktailId, user, accessToken); + } = useRecipeComments(cocktailId, user); return (
diff --git a/src/domains/shared/store/auth.ts b/src/domains/shared/store/auth.ts index 01a39e54..eebb8548 100644 --- a/src/domains/shared/store/auth.ts +++ b/src/domains/shared/store/auth.ts @@ -1,6 +1,5 @@ import { getApi } from '@/app/api/config/appConfig'; import { create } from 'zustand'; -import { persist } from 'zustand/middleware'; export interface User { id: string; @@ -13,41 +12,44 @@ export interface User { interface AuthState { user: User | null; - accessToken: string | null; isLoggedIn: boolean; - setUser: (user: User, token: string) => void; + setUser: (user: User) => void; logout: () => Promise; loginWithProvider: (provider: User['provider']) => void; updateUser: () => Promise; + checkAuth: () => Promise; } export const useAuthStore = create()((set) => ({ user: null, - accessToken: null, isLoggedIn: false, loginWithProvider: (provider) => { window.location.href = `${getApi}/oauth2/authorization/${provider}`; }, - setUser: (user, token) => { - const updatedUser = { ...user, abv_degree: 5.0 }; - set({ user: updatedUser, accessToken: token, isLoggedIn: true }); + setUser: (user) => { + const updatedUser = { ...user, abv_degree: user.abv_degree ?? 5.0 }; + set({ user: updatedUser, isLoggedIn: true }); }, + // 로그아웃 logout: async () => { try { await fetch(`${getApi}/user/auth/logout`, { method: 'POST', credentials: 'include', }); - set({ user: null, accessToken: null, isLoggedIn: false }); + set({ user: null, isLoggedIn: false }); } catch (err) { console.error('로그아웃 실패', err); + } finally { + set({ user: null, isLoggedIn: false }); } }, + // idle + refresh 시 호출 updateUser: async () => { try { const res = await fetch(`${getApi}/user/auth/refresh`, { @@ -57,18 +59,40 @@ export const useAuthStore = create()((set) => ({ }); if (!res.ok) throw new Error('토큰 갱신 실패'); + const data = await res.json(); const userInfo = data?.data?.user; - const accessToken = data?.data?.accessToken; - if (userInfo && accessToken) { - set({ user: userInfo, accessToken, isLoggedIn: true }); + if (userInfo) { + set({ user: userInfo, isLoggedIn: true }); return userInfo; } return null; } catch (err) { console.error('updateUser 실패', err); - set({ accessToken: null, user: null, isLoggedIn: false }); + set({ user: null, isLoggedIn: false }); + return null; + } + }, + + // 시작 시 로그인 상태 확인 + checkAuth: async () => { + try { + const res = await fetch(`${getApi}/user/auth/me`, { + method: 'GET', + credentials: 'include', + }); + if (!res.ok) throw new Error('인증 실패'); + + const data = await res.json(); + const userInfo = data?.data?.user; + if (userInfo) { + set({ user: userInfo, isLoggedIn: true }); + return userInfo; + } + return null; + } catch { + set({ user: null, isLoggedIn: false }); return null; } }, From bead7fa9dea1602e2b03d93c0234fcf3c8f658d9 Mon Sep 17 00:00:00 2001 From: ahk0413 Date: Mon, 13 Oct 2025 14:51:14 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[test]=20idle=201=EB=B6=84=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EA=B0=90=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domains/login/hook/useIdleLogout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domains/login/hook/useIdleLogout.ts b/src/domains/login/hook/useIdleLogout.ts index c2cfd493..d8708917 100644 --- a/src/domains/login/hook/useIdleLogout.ts +++ b/src/domains/login/hook/useIdleLogout.ts @@ -5,7 +5,7 @@ import { useLogout } from './useLogout'; import { useToast } from '@/shared/hook/useToast'; import { useAuthStore } from '@/domains/shared/store/auth'; -const IDLE_TIMEOUT = 4 * 60 * 60 * 1000; +const IDLE_TIMEOUT = 1 * 60 * 1000; export const useIdleLogout = () => { const handleLogout = useLogout();