Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/assets/components/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +15 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

인증 상태가 비동기적으로 초기화될 때를 대비한 initialized 플래그는 좋은 패턴입니다. 하지만 현재 AuthContext에서는 useState의 지연 초기화(lazy initializer)를 통해 동기적으로 상태를 설정하고 있어 첫 렌더링부터 isAuthenticated 값이 정확합니다.

현재 이 if (!initialized) 확인 로직은 불필요하며, 인증된 사용자가 페이지를 새로고침할 때마다 잠시 빈 화면을 보여주게 되어 사용자 경험을 저해할 수 있습니다.

따라서 AuthContextProtectedRoute 양쪽에서 initialized 관련 로직을 모두 제거하여 코드를 단순화하고 사용자 경험을 개선하는 것을 제안합니다. 나중에 비동기 인증 로직이 추가될 때 이 패턴을 다시 도입하는 것이 좋겠습니다.

Suggested change
const { isAuthenticated, user, initialized } = useAuth();
// while auth state is initializing, don't redirect — avoid false negatives
if (!initialized) return null;
const { isAuthenticated, user } = useAuth();

if (!isAuthenticated) {
return <Navigate to="/" replace />;
}
Expand Down
53 changes: 29 additions & 24 deletions src/assets/components/modal/CheckReportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReportDetail>(
`/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<ReportDetail>(
`/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;
Expand Down
7 changes: 7 additions & 0 deletions src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface User {
interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
initialized: boolean;
login: (user: User, token?: string) => void;
logout: () => void;
}
Expand All @@ -34,6 +35,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
return null;
});
const [initialized] = useState(true);

const login = (userData: User, token?: string) => {
setUser(userData);
Expand All @@ -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 (
<AuthContext.Provider
value={{
user,
isAuthenticated: !!user,
initialized,
login,
logout,
}}
Expand Down
Loading