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/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 ae8c5a79..551da576 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] = useState(true);
const login = (userData: User, token?: string) => {
setUser(userData);
@@ -49,11 +51,16 @@ 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)
+
return (