From 10291919d8f991333c8d2bdf54a9fdd2b13d9774 Mon Sep 17 00:00:00 2001 From: Joinsung Date: Mon, 23 Jun 2025 05:05:59 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9Bfix:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A6=AC=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EB=9E=99=ED=8A=B8=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/error.tsx | 16 ++++++++++++++-- src/app/not-found.tsx | 25 ++++++++++++++++++------- src/app/shared/components/Redirect.tsx | 25 +++++++++++++------------ 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/app/error.tsx b/src/app/error.tsx index 3eb337a..2c03c1b 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -2,16 +2,28 @@ import { useMounted } from '@hooks/useMounted' import Image from 'next/image' +import { useRouter } from 'next/navigation' import { useTheme } from 'next-themes' export default function ErrorPage({ reset }: { reset?: () => void }) { const { theme, systemTheme } = useTheme() + const router = useRouter() const mounted = useMounted() if (!mounted) return null const currentTheme = theme === 'system' ? systemTheme : theme const isDark = currentTheme === 'dark' + const handleClick = () => { + if (reset) { + reset() + } else { + router.back() + } + } + + const buttonText = reset ? '다시 시도하기' : '이전 페이지로' + return (
void }) { ? 'mt-8 rounded-lg bg-blue-700 px-8 py-3 text-lg font-semibold text-white shadow transition-colors hover:bg-blue-800' : 'mt-8 rounded-lg bg-blue-600 px-8 py-3 text-lg font-semibold text-white shadow transition-colors hover:bg-blue-700' } - onClick={() => (reset ? reset() : window.history.back())} + onClick={handleClick} > - 이전 페이지로 + {buttonText}
) diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index a2815fb..7e0f064 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -2,16 +2,31 @@ import { useMounted } from '@hooks/useMounted' import Image from 'next/image' +import { useRouter } from 'next/navigation' import { useTheme } from 'next-themes' +import { useAuthStore } from '@/app/features/auth/store/useAuthStore' + export default function NotFound() { const { theme, systemTheme } = useTheme() const mounted = useMounted() + const router = useRouter() + const { isLoggedIn } = useAuthStore() + if (!mounted) return null const currentTheme = theme === 'system' ? systemTheme : theme const isDark = currentTheme === 'dark' + const buttonText = isLoggedIn ? '대시보드로 이동' : '메인으로 이동' + const buttonClass = isDark + ? isLoggedIn + ? 'bg-blue-700 hover:bg-blue-800' + : 'bg-blue-500 hover:bg-blue-600' + : isLoggedIn + ? 'bg-blue-600 hover:bg-blue-700' + : 'bg-blue-400 hover:bg-blue-500' + return (
) diff --git a/src/app/shared/components/Redirect.tsx b/src/app/shared/components/Redirect.tsx index 983246f..6beb8f1 100644 --- a/src/app/shared/components/Redirect.tsx +++ b/src/app/shared/components/Redirect.tsx @@ -10,6 +10,14 @@ import { useAuthStore } from '@/app/features/auth/store/useAuthStore' // 로그인 없이 접근 가능한 경로 const PUBLIC_ROUTES = ['/login', '/signup'] +// 보호 경로: 로그인 필요, 정규식 기반 +const PROTECTED_ROUTE_PATTERNS = [ + /^\/dashboard\/[^/]+$/, // /dashboard/:id + /^\/dashboard\/[^/]+\/edit$/, // /dashboard/:id/edit + /^\/mypage$/, // /mypage + /^\/mydashboard$/, // /mydashboard +] + export default function Redirect({ children }: { children: React.ReactNode }) { const router = useRouter() const pathname = usePathname() @@ -20,11 +28,12 @@ export default function Redirect({ children }: { children: React.ReactNode }) { const { data: firstDashboardId, isSuccess } = useFirstDashboardIdQuery() - // ✅ 경로 파생값 선언 (중복 제거) const isRoot = pathname === '/' const isPublic = PUBLIC_ROUTES.includes(pathname) + const isProtectedRoute = PROTECTED_ROUTE_PATTERNS.some((pattern) => + pattern.test(pathname), + ) - // 경로 변경 시 redirecting 상태 초기화 useEffect(() => { if (prevPath.current !== pathname) { setRedirecting(false) @@ -32,21 +41,17 @@ export default function Redirect({ children }: { children: React.ReactNode }) { } }, [pathname]) - // 로그인 상태와 경로에 따른 리다이렉트 처리 useEffect(() => { if (!mounted || redirecting) return - // 1. 비로그인 + 루트(/): 랜딩 페이지 접근 허용 if (!isLoggedIn && isRoot) return - // 2. 비로그인 + 보호 경로: 로그인 페이지로 이동 - if (!isLoggedIn && !isPublic && !isRoot) { + if (!isLoggedIn && isProtectedRoute) { setRedirecting(true) router.replace('/login') return } - // 3. 로그인 + 루트(/): 대시보드 또는 마이대시보드로 이동 if (isLoggedIn && isRoot) { if (!isSuccess) return setRedirecting(true) @@ -56,14 +61,11 @@ export default function Redirect({ children }: { children: React.ReactNode }) { return } - // 4. 로그인 + 퍼블릭 경로: 마이대시보드로 이동 if (isLoggedIn && isPublic) { setRedirecting(true) router.replace('/mydashboard') return } - - // 5. 나머지는 접근 허용 }, [ pathname, isLoggedIn, @@ -74,11 +76,10 @@ export default function Redirect({ children }: { children: React.ReactNode }) { firstDashboardId, isRoot, isPublic, + isProtectedRoute, ]) - // 🔒 깜빡임 방지: 루트 경로만 예외로 즉시 렌더링 허용 if (!mounted && !isRoot) return null - // ✅ 최종 렌더링 return <>{children} }