diff --git a/src/App.tsx b/src/App.tsx index 699b696..0869d26 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ - import React from 'react'; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import './App.css'; @@ -19,6 +18,7 @@ import Analytics from './pages/Analytics'; import PaymentMethods from './pages/PaymentMethods'; import Settings from './pages/Settings'; import { BudgetProvider } from './contexts/BudgetContext'; +import PrivateRoute from './components/auth/PrivateRoute'; // 전역 오류 핸들러 const handleError = (error: any) => { @@ -83,15 +83,39 @@ function App() { } /> } /> } /> - } /> + + + + } /> } /> - } /> - } /> - } /> + + + + } /> + + + + } /> + + + + } /> } /> } /> - } /> - } /> + + + + } /> + + + + } /> } /> diff --git a/src/components/auth/PrivateRoute.tsx b/src/components/auth/PrivateRoute.tsx new file mode 100644 index 0000000..75c16bc --- /dev/null +++ b/src/components/auth/PrivateRoute.tsx @@ -0,0 +1,40 @@ + +import { ReactNode, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useAuth } from '@/contexts/auth'; +import { useToast } from '@/hooks/useToast.wrapper'; + +interface PrivateRouteProps { + children: ReactNode; + requireAuth?: boolean; +} + +/** + * 인증이 필요한 라우트를 보호하는 컴포넌트 + */ +const PrivateRoute = ({ children, requireAuth = true }: PrivateRouteProps) => { + const { user, loading } = useAuth(); + const navigate = useNavigate(); + const { toast } = useToast(); + + useEffect(() => { + // 로딩 중이 아니고, 인증이 필요한데 사용자가 로그인되어 있지 않은 경우 + if (!loading && requireAuth && !user) { + toast({ + title: "로그인 필요", + description: "이 페이지에 접근하려면 로그인이 필요합니다.", + variant: "destructive", + }); + navigate('/login', { replace: true }); + } + }, [user, loading, navigate, requireAuth, toast]); + + // 로딩 중이거나 인증이 필요하지만 사용자가 로그인되어 있지 않은 경우 아무것도 렌더링하지 않음 + if (loading || (requireAuth && !user)) { + return null; + } + + return <>{children}; +}; + +export default PrivateRoute; diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx index cda36da..c058c18 100644 --- a/src/pages/NotFound.tsx +++ b/src/pages/NotFound.tsx @@ -1,24 +1,51 @@ -import { useLocation } from "react-router-dom"; + +import { useLocation, useNavigate } from "react-router-dom"; import { useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { FileQuestion, Home } from "lucide-react"; const NotFound = () => { const location = useLocation(); + const navigate = useNavigate(); useEffect(() => { console.error( - "404 Error: User attempted to access non-existent route:", + "404 Error: 존재하지 않는 경로에 접근 시도:", location.pathname ); }, [location.pathname]); return ( -
-
-

404

-

Oops! Page not found

- - Return to Home - +
+
+
+
+ +
+ +

페이지를 찾을 수 없습니다

+

+ 요청하신 페이지가 존재하지 않거나 접근 권한이 없습니다. + 이동하려는 주소가 올바른지 확인해주세요. +

+ +
+ + + +
+
); diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index adf438f..2da0be5 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,3 +1,4 @@ + import React from 'react'; import { useNavigate } from 'react-router-dom'; import NavBar from '@/components/NavBar'; @@ -5,21 +6,30 @@ import SyncSettings from '@/components/SyncSettings'; import { User, CreditCard, Bell, Lock, HelpCircle, LogOut, ChevronRight } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useAuth } from '@/contexts/auth'; +import { useToast } from '@/hooks/use-toast'; const SettingsOption = ({ icon: Icon, label, description, onClick, - color = "text-neuro-income" + color = "text-neuro-income", + disabled = false }: { icon: React.ElementType; label: string; description?: string; onClick?: () => void; color?: string; + disabled?: boolean; }) => { - return
+ return
@@ -36,12 +46,26 @@ const SettingsOption = ({ const Settings = () => { const navigate = useNavigate(); const { user, signOut } = useAuth(); + const { toast } = useToast(); const handleLogout = async () => { await signOut(); navigate('/login'); }; + const handleProtectedRouteClick = (path: string, label: string) => { + if (!user) { + toast({ + title: "로그인 필요", + description: `${label}에 접근하려면 로그인이 필요합니다.`, + variant: "destructive", + }); + navigate('/login'); + } else { + navigate(path); + } + }; + return
{/* Header */} @@ -73,15 +97,40 @@ const Settings = () => { {/* Settings Options */}

계정

- navigate('/profile-management')} /> - navigate('/payment-methods')} /> - navigate('/notification-settings')} /> + handleProtectedRouteClick('/profile', '프로필 관리')} + /> + handleProtectedRouteClick('/payment-methods', '결제 방법')} + /> + handleProtectedRouteClick('/notifications', '알림 설정')} + />

앱 설정

- navigate('/security-privacy-settings')} /> - navigate('/help-support')} /> + handleProtectedRouteClick('/security-privacy', '보안 및 개인정보')} + /> + navigate('/help-support')} + />