From 8e3802dc052d3c1b80e863e8aafb60ff94aa3e51 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Mon, 29 Dec 2025 14:31:03 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20ProtectedRoute=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=83=81=ED=83=9C=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/components/ProtectedRoute.tsx | 4 +++- src/contexts/AuthContext.tsx | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/assets/components/ProtectedRoute.tsx b/src/assets/components/ProtectedRoute.tsx index 723eee1c..13d6d562 100644 --- a/src/assets/components/ProtectedRoute.tsx +++ b/src/assets/components/ProtectedRoute.tsx @@ -12,7 +12,9 @@ export default function ProtectedRoute({ children, requiredRole, }: ProtectedRouteProps) { - const { isAuthenticated, user } = useAuth(); + const { isAuthenticated, user, initialized } = useAuth(); + // while auth state is initializing, don't redirect — avoid false negatives + if (!initialized) return null; if (!isAuthenticated) { return ; } diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index ae8c5a79..18562225 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -11,6 +11,7 @@ interface User { interface AuthContextType { user: User | null; isAuthenticated: boolean; + initialized: boolean; login: (user: User, token?: string) => void; logout: () => void; } @@ -34,6 +35,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { } return null; }); + const [initialized, setInitialized] = useState(false); const login = (userData: User, token?: string) => { setUser(userData); @@ -49,11 +51,19 @@ export function AuthProvider({ children }: { children: ReactNode }) { localStorage.removeItem('token'); }; + // Mark context as initialized after mount so consumers don't redirect during startup + // (localStorage read above is synchronous, but this ensures any async checks + // or token refresh logic can set state before ProtectedRoute redirects) + useEffect(() => { + setInitialized(true); + }, []); + return ( Date: Mon, 29 Dec 2025 14:32:52 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20AuthContext=EC=97=90=EC=84=9C=20useE?= =?UTF-8?q?ffect=20=ED=9B=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/contexts/AuthContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 18562225..63268163 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useState } from 'react'; +import { createContext, useContext, useState, useEffect } from 'react'; import type { ReactNode } from 'react'; interface User { From 3ba89730332fecb4f239f3fd86b1da63d49cd7b2 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Mon, 29 Dec 2025 14:35:34 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20CheckReportModal=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EB=B9=84=EB=8F=99=EA=B8=B0=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EB=A1=9C=EB=94=A9=20=EB=B0=8F=20=EC=98=A4=EB=A5=98=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/modal/CheckReportModal.tsx | 53 ++++++++++--------- src/contexts/AuthContext.tsx | 5 +- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/assets/components/modal/CheckReportModal.tsx b/src/assets/components/modal/CheckReportModal.tsx index 8b943463..36c007eb 100644 --- a/src/assets/components/modal/CheckReportModal.tsx +++ b/src/assets/components/modal/CheckReportModal.tsx @@ -31,33 +31,38 @@ export default function CheckReportModal({ const [processing, setProcessing] = useState(false); useEffect(() => { - fetchReportDetail(); - }, [reportId]); - - const fetchReportDetail = async () => { - try { - setLoading(true); - const response = await instance.get( - `/api/admin/report/${reportId}` - ); - setReportDetail(response.data); - } catch (err) { - if (err instanceof AxiosError) { - if (err.response?.status === 403) { - toast.error('관리자 권한이 없습니다.'); - } else if (err.response?.status === 404) { - toast.error('신고를 찾을 수 없습니다.'); + let mounted = true; + const fetchReportDetail = async () => { + try { + setLoading(true); + const response = await instance.get( + `/api/admin/report/${reportId}` + ); + if (!mounted) return; + setReportDetail(response.data); + } catch (err) { + if (err instanceof AxiosError) { + if (err.response?.status === 403) { + toast.error('관리자 권한이 없습니다.'); + } else if (err.response?.status === 404) { + toast.error('신고를 찾을 수 없습니다.'); + } else { + toast.error('신고 상세 정보를 불러오는데 실패했습니다.'); + } } else { - toast.error('신고 상세 정보를 불러오는데 실패했습니다.'); + toast.error('오류가 발생했습니다.'); } - } else { - toast.error('오류가 발생했습니다.'); + onClose(); + } finally { + if (mounted) setLoading(false); } - onClose(); - } finally { - setLoading(false); - } - }; + }; + + fetchReportDetail(); + return () => { + mounted = false; + }; + }, [reportId, onClose]); const handleReportAction = async (action: 'BLOCK' | 'REJECT' | 'HOLD') => { if (!reportDetail || processing) return; diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 63268163..19afd891 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -35,7 +35,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { } return null; }); - const [initialized, setInitialized] = useState(false); + const [initialized] = useState(true); const login = (userData: User, token?: string) => { setUser(userData); @@ -54,9 +54,6 @@ export function AuthProvider({ children }: { children: ReactNode }) { // Mark context as initialized after mount so consumers don't redirect during startup // (localStorage read above is synchronous, but this ensures any async checks // or token refresh logic can set state before ProtectedRoute redirects) - useEffect(() => { - setInitialized(true); - }, []); return ( Date: Mon, 29 Dec 2025 14:36:54 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20AuthContext=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20useEffect=20=ED=9B=85=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/contexts/AuthContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 19afd891..b7490a47 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useState, useEffect } from 'react'; +import { createContext, useContext, useState } from 'react'; import type { ReactNode } from 'react'; interface User { From 5b67bc00a9a1ca62905a70c84d0290d28dce3744 Mon Sep 17 00:00:00 2001 From: koreahghg Date: Mon, 29 Dec 2025 14:38:14 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20AuthContext=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A0=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/contexts/AuthContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index b7490a47..551da576 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -35,7 +35,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { } return null; }); - const [initialized] = useState(true); + const [initialized] = useState(true); const login = (userData: User, token?: string) => { setUser(userData);