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
102 changes: 94 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,99 @@
import { RouterProvider } from 'react-router-dom';
import { ThemeProvider, CssBaseline } from '@mui/material';
import { theme } from './styles/theme';
import { router } from './routes';
import { useEffect } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

import { ProtectedRoute, AdminRoute, PublicOnlyRoute } from './components/RouteGuards';
import { useAuthStore } from './store/useAuthStore';

// common page
import AppLayout from './components/AppLayout';
import HomePage from './pages/HomePage';
import NotFoundPage from './pages/NotFoundPage';
import MatchesPage from './pages/MatchesPage';
import AuthCallbackPage from './pages/AuthCallbackPage';

// public only page
import SignupPage from './pages/SignupPage';
import LoginPage from './pages/LoginPage';

// protected page
import ProfilePage from './pages/ProfilePage';
import MyMatchesPage from './pages/MyMatchesPage';

// admin page
import CreateMatchPage from './pages/CreateMatchPage';

export default function App() {
useEffect(() => {
useAuthStore.getState().syncFromStorage();
}, []);

return (
<ThemeProvider theme={theme}>
<CssBaseline />
<RouterProvider router={router} />
</ThemeProvider>
<BrowserRouter>
<Routes>
<Route path="/auth/callback" element={<AuthCallbackPage />} />

{/* AppLayout์„ ์ ์šฉํ•˜๋Š” ํŽ˜์ด์ง€ */}
<Route path="/" element={<AppLayout />}>
{/* ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ํŽ˜์ด์ง€ */}
<Route path="/" element={<HomePage />} />
<Route path="/home" element={<HomePage />} />
<Route path="/notfound" element={<NotFoundPage />} />
<Route path="/matches" element={<MatchesPage />} />

{/* ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ */}
<Route
path="/login"
element={
<PublicOnlyRoute>
<LoginPage />
</PublicOnlyRoute>
}
/>
<Route
path="/signup"
element={
<PublicOnlyRoute>
<SignupPage />
</PublicOnlyRoute>
}
/>
{/* <Route
path="/auth/callback"
element={
<ProtectedRoute>
<AuthCallbackPage />
</ProtectedRoute>}
/> */}

{/* ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ */}
<Route
path="/profile"
element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
}
/>
<Route
path="/mymatches"
element={
<ProtectedRoute>
<MyMatchesPage />
</ProtectedRoute>
}
/>

{/* ๊ด€๋ฆฌ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ */}
<Route
path="/createMatch"
element={
<AdminRoute>
<CreateMatchPage />
</AdminRoute>
}
/>
</Route>
</Routes>
</BrowserRouter>
);
}
23 changes: 0 additions & 23 deletions src/components/ProtectedRoute.tsx

This file was deleted.

94 changes: 94 additions & 0 deletions src/components/RouteGuards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { ReactNode } from 'react';
import type { UserRole } from '../types/index';
import { useEffect, useState } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { useAuthStore } from '../store/useAuthStore';

// ๊ณตํ†ต Props ํƒ€์ž…
interface RouteGuardProps {
children: ReactNode;
}

// ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ
export const ProtectedRoute = ({ children }: RouteGuardProps) => {
const { isAuthenticated, tokens } = useAuthStore();
const location = useLocation();

const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
// ํ† ํฐ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๋กœ๋”ฉ ์ƒํƒœ ํ•ด์ œ
const timer = setTimeout(() => {
setIsLoading(false);
}, 100); // ์งง์€ ์ง€์—ฐ์œผ๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๋Œ€๊ธฐ

return () => clearTimeout(timer);
}, [tokens]);

// ์•„์ง ์ธ์ฆ ์ƒํƒœ๋ฅผ ํ™•์ธ ์ค‘์ด๋ฉด ๋กœ๋”ฉ ํ‘œ์‹œ
if (isLoading) {
return <div>๋กœ๋”ฉ ์ค‘...</div>;
}

// ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
// state์— ํ˜„์žฌ ์œ„์น˜๋ฅผ ์ €์žฅํ•ด์„œ ๋กœ๊ทธ์ธ ํ›„ ๋‹ค์‹œ ๋Œ์•„์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ํ•จ
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}

// ๋กœ๊ทธ์ธํ–ˆ๋‹ค๋ฉด ์›๋ž˜ ๋ณด๋ ค๋˜ ํŽ˜์ด์ง€ ํ‘œ์‹œ
return <>{children}</>;
};

// ํŠน์ • ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ
interface RoleGuardProps extends RouteGuardProps {
requiredRole: UserRole;
fallbackPath?: string; // ๊ถŒํ•œ์ด ์—†์„ ๋•Œ ์ด๋™ํ•  ๊ฒฝ๋กœ
}

export const RoleGuard = ({ children, requiredRole, fallbackPath = '/' }: RoleGuardProps) => {
const { user, isAuthenticated } = useAuthStore();
const location = useLocation();

// if (isLoading) {
// return <div>๋กœ๋”ฉ ์ค‘...</div>;
// }

// ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}

// ๋กœ๊ทธ์ธํ–ˆ์ง€๋งŒ ๊ถŒํ•œ์ด ์—†๋‹ค๋ฉด fallback ๊ฒฝ๋กœ๋กœ
if (user?.role !== requiredRole) {
return <Navigate to={fallbackPath} replace />;
}

// ๋ชจ๋“  ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋ฉด ์›๋ž˜ ํŽ˜์ด์ง€ ํ‘œ์‹œ
return <>{children}</>;
};

// ๊ด€๋ฆฌ์ž ์ „์šฉ ํŽ˜์ด์ง€๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ (RoleGuard์˜ ํŠนํ™” ๋ฒ„์ „)
export const AdminRoute = ({ children }: RouteGuardProps) => {
return (
<RoleGuard requiredRole="ADMIN" fallbackPath="/unauthorized">
{children}
</RoleGuard>
);
};

// ์ด๋ฏธ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผํ•˜๋ฉด ์•ˆ ๋˜๋Š” ํŽ˜์ด์ง€ (๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž… ๋“ฑ)
export const PublicOnlyRoute = ({ children }: RouteGuardProps) => {
const { isAuthenticated } = useAuthStore();

// if (isLoading) {
// return <div>๋กœ๋”ฉ ์ค‘...</div>;
// }

// ์ด๋ฏธ ๋กœ๊ทธ์ธํ–ˆ๋‹ค๋ฉด ํ™ˆ์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
if (isAuthenticated) {
return <Navigate to="/" replace />;
}

return <>{children}</>;
};
1 change: 1 addition & 0 deletions src/pages/AuthCallbackPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default function AuthCallbackPage() {
} catch (e: unknown) {
const message = e instanceof Error ? e.message : '์ธ์ฆ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.';
setError(message);
navigate('/login', { replace: true });
}
})();
}, [navigate]);
Expand Down
58 changes: 0 additions & 58 deletions src/routes/index.tsx

This file was deleted.