access_token 쿠키 확인하여 로그인 상태 유지 처리
ProtectedRoute 로 로그인 필요 페이지 접근 관리 import 문을 src/ 포함된 절대경로로 개선
This commit is contained in:
35
src/App.tsx
35
src/App.tsx
@@ -1,22 +1,23 @@
|
|||||||
import React, { Suspense, useEffect } from 'react'
|
import React, { Suspense, useEffect } from 'react'
|
||||||
import { HashRouter, Route, Routes } from 'react-router-dom'
|
import { HashRouter, Route, Routes } from 'react-router-dom'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { RootState } from './store'
|
import { RootState } from 'src/store'
|
||||||
|
|
||||||
import { CSpinner, useColorModes } from '@coreui/react'
|
import { CSpinner, useColorModes } from '@coreui/react'
|
||||||
import './scss/style.scss'
|
import 'src/scss/style.scss'
|
||||||
|
|
||||||
// We use those styles to show code examples, you should remove them in your application.
|
// We use those styles to show code examples, you should remove them in your application.
|
||||||
import './scss/examples.scss'
|
import 'src/scss/examples.scss'
|
||||||
|
|
||||||
// Containers
|
// Containers
|
||||||
const DefaultLayout = React.lazy(() => import('./layout/DefaultLayout'))
|
const DefaultLayout = React.lazy(() => import('src/layout/DefaultLayout'))
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
const Login = React.lazy(() => import('./views/pages/login/Login'))
|
const Login = React.lazy(() => import('src/views/pages/login/Login'))
|
||||||
const Register = React.lazy(() => import('./views/pages/register/Register'))
|
const Register = React.lazy(() => import('src/views/pages/register/Register'))
|
||||||
const Page404 = React.lazy(() => import('./views/pages/page404/Page404'))
|
const Page404 = React.lazy(() => import('src/views/pages/page404/Page404'))
|
||||||
const Page500 = React.lazy(() => import('./views/pages/page500/Page500'))
|
const Page500 = React.lazy(() => import('src/views/pages/page500/Page500'))
|
||||||
|
const ProtectedRoute = React.lazy(() => import('src/routes/ProtectedRoute'))
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { isColorModeSet, setColorMode } = useColorModes('coreui-free-react-admin-template-theme')
|
const { isColorModeSet, setColorMode } = useColorModes('coreui-free-react-admin-template-theme')
|
||||||
@@ -47,10 +48,24 @@ const App = () => {
|
|||||||
>
|
>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/register" element={<Register />} />
|
<Route
|
||||||
|
path="/register"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<Register />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route path="/404" element={<Page404 />} />
|
<Route path="/404" element={<Page404 />} />
|
||||||
<Route path="/500" element={<Page500 />} />
|
<Route path="/500" element={<Page500 />} />
|
||||||
<Route path="*" element={<DefaultLayout />} />
|
<Route
|
||||||
|
path="*"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<DefaultLayout />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import axios from './axios';
|
import axios from 'src/axios/axios';
|
||||||
import { jwtDecode } from 'jwt-decode';
|
import { jwtDecode } from 'jwt-decode';
|
||||||
|
|
||||||
export const getAccessTokenFromCookie = (): string | null => {
|
export const getAccessTokenFromCookie = (): string | null => {
|
||||||
@@ -30,7 +30,7 @@ export interface DecodedToken {
|
|||||||
encryptedPayload: string;
|
encryptedPayload: string;
|
||||||
memberId: string;
|
memberId: string;
|
||||||
memberName: string;
|
memberName: string;
|
||||||
expiration: number;
|
exp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 로그인 API
|
// 로그인 API
|
||||||
@@ -48,8 +48,8 @@ export const register = async (data: RegisterData): Promise<{ message: string }>
|
|||||||
// 로그아웃 API
|
// 로그아웃 API
|
||||||
export const logout = async (): Promise<void> => {
|
export const logout = async (): Promise<void> => {
|
||||||
await axios.post('/auth/logout');
|
await axios.post('/auth/logout');
|
||||||
//localStorage.removeItem('access_token');
|
localStorage.removeItem('accessToken');
|
||||||
//localStorage.removeItem('refresh_token');
|
localStorage.removeItem('refreshToken');
|
||||||
document.cookie = 'access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';
|
document.cookie = 'access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';
|
||||||
document.cookie = 'refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';
|
document.cookie = 'refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';
|
||||||
};
|
};
|
||||||
@@ -60,7 +60,9 @@ export const isTokenValid = (token: string): boolean => {
|
|||||||
const decoded = jwtDecode<DecodedToken>(token);
|
const decoded = jwtDecode<DecodedToken>(token);
|
||||||
const currentTime = Date.now() / 1000;
|
const currentTime = Date.now() / 1000;
|
||||||
|
|
||||||
return decoded.expiration > currentTime;
|
if (!decoded.exp) return false; // 만료 정보가 없으면 일단 유효하다고 판단하거나, 정책에 따라 변경 가능
|
||||||
|
|
||||||
|
return decoded.exp > currentTime;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -75,9 +77,3 @@ export const getUserFromToken = (token: string): DecodedToken | null => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 현재 사용자 정보 가져오기
|
|
||||||
export const getCurrentUser = async () => {
|
|
||||||
const response = await axios.get('/auth/me');
|
|
||||||
return response.data;
|
|
||||||
};
|
|
||||||
@@ -14,7 +14,7 @@ const instance = axios.create({
|
|||||||
// 요청 인터셉터
|
// 요청 인터셉터
|
||||||
instance.interceptors.request.use(
|
instance.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const accessToken = localStorage.getItem('access_token');
|
const accessToken = localStorage.getItem('accessToken') || localStorage.getItem('access_token');
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
config.headers.Authorization = `Bearer ${accessToken}`;
|
config.headers.Authorization = `Bearer ${accessToken}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import routes from '../routes'
|
import routes from 'src/routes/routes'
|
||||||
|
|
||||||
import { CBreadcrumb, CBreadcrumbItem } from '@coreui/react'
|
import { CBreadcrumb, CBreadcrumbItem } from '@coreui/react'
|
||||||
|
|
||||||
|
interface Route {
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
exact?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const AppBreadcrumb = () => {
|
const AppBreadcrumb = () => {
|
||||||
const currentLocation = useLocation().pathname
|
const currentLocation = useLocation().pathname
|
||||||
|
|
||||||
const getRouteName = (pathname, routes) => {
|
const getRouteName = (pathname: string, routes: Route[]): string | false => {
|
||||||
const currentRoute = routes.find((route) => route.path === pathname)
|
const currentRoute = routes.find((route) => route.path === pathname)
|
||||||
return currentRoute ? currentRoute.name : false
|
return currentRoute ? currentRoute.name : false
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBreadcrumbs = (location) => {
|
const getBreadcrumbs = (location: string) => {
|
||||||
const breadcrumbs = []
|
const breadcrumbs: { pathname: string; name: string; active: boolean }[] = []
|
||||||
location.split('/').reduce((prev, curr, index, array) => {
|
location.split('/').reduce((prev: string, curr: string, index: number, array: string[]) => {
|
||||||
const currentPathname = `${prev}/${curr}`
|
const currentPathname = `${prev}/${curr}`
|
||||||
const routeName = getRouteName(currentPathname, routes)
|
const routeName = getRouteName(currentPathname, routes as Route[])
|
||||||
routeName &&
|
routeName &&
|
||||||
breadcrumbs.push({
|
breadcrumbs.push({
|
||||||
pathname: currentPathname,
|
pathname: currentPathname,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'
|
|||||||
import { CContainer, CSpinner } from '@coreui/react'
|
import { CContainer, CSpinner } from '@coreui/react'
|
||||||
|
|
||||||
// routes config
|
// routes config
|
||||||
import routes from '../routes'
|
import routes from 'src/routes/routes'
|
||||||
|
|
||||||
const AppContent = () => {
|
const AppContent = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import { NavLink } from 'react-router-dom'
|
import { NavLink } from 'react-router-dom'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import { RootState } from '../store'
|
import { RootState } from 'src/store'
|
||||||
import {
|
import {
|
||||||
CContainer,
|
CContainer,
|
||||||
CDropdown,
|
CDropdown,
|
||||||
@@ -26,8 +26,8 @@ import {
|
|||||||
cilSun,
|
cilSun,
|
||||||
} from '@coreui/icons'
|
} from '@coreui/icons'
|
||||||
|
|
||||||
import { AppBreadcrumb } from './index'
|
import { AppBreadcrumb } from 'src/components/index'
|
||||||
import { AppHeaderDropdown } from './header/index'
|
import { AppHeaderDropdown } from 'src/components/header/index'
|
||||||
import { useAuth } from 'src/hooks/useAuth'
|
import { useAuth } from 'src/hooks/useAuth'
|
||||||
|
|
||||||
const AppHeader = () => {
|
const AppHeader = () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { NavLink } from 'react-router-dom'
|
import { NavLink } from 'react-router-dom'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import { RootState } from '../store'
|
import { RootState } from 'src/store'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CCloseButton,
|
CCloseButton,
|
||||||
@@ -13,13 +13,13 @@ import {
|
|||||||
} from '@coreui/react'
|
} from '@coreui/react'
|
||||||
import CIcon from '@coreui/icons-react'
|
import CIcon from '@coreui/icons-react'
|
||||||
|
|
||||||
import { AppSidebarNav } from './AppSidebarNav'
|
import { AppSidebarNav } from 'src/components/AppSidebarNav'
|
||||||
|
|
||||||
import { logo } from 'src/assets/brand/logo'
|
import { logo } from 'src/assets/brand/logo'
|
||||||
import { sygnet } from 'src/assets/brand/sygnet'
|
import { sygnet } from 'src/assets/brand/sygnet'
|
||||||
|
|
||||||
// sidebar nav config
|
// sidebar nav config
|
||||||
import navigation from '../_nav'
|
import navigation from 'src/_nav'
|
||||||
|
|
||||||
const AppSidebar = () => {
|
const AppSidebar = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
import ComponentsImg from 'src/assets/images/components.webp'
|
|
||||||
|
|
||||||
const DocsComponents = (props) => (
|
|
||||||
<div className="bg-primary bg-opacity-10 border border-2 border-primary rounded mb-4">
|
|
||||||
<div className="row d-flex align-items-center p-3 px-xl-4 flex-xl-nowrap">
|
|
||||||
<div className="col-xl-auto col-12 d-none d-xl-block p-0">
|
|
||||||
<img
|
|
||||||
className="img-fluid"
|
|
||||||
src={ComponentsImg}
|
|
||||||
width="160px"
|
|
||||||
height="160px"
|
|
||||||
alt="CoreUI PRO hexagon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-md col-12 px-lg-4">
|
|
||||||
Our Admin Panel isn’t just a mix of third-party components. It’s{' '}
|
|
||||||
<strong>
|
|
||||||
the only open-source React dashboard built on a professional, enterprise-grade UI
|
|
||||||
Components Library
|
|
||||||
</strong>
|
|
||||||
. This component is part of this library, and we present only the basic usage of it here. To
|
|
||||||
explore extended examples, detailed API documentation, and customization options, refer to
|
|
||||||
our docs.
|
|
||||||
</div>
|
|
||||||
<div className="col-md-auto col-12 mt-3 mt-lg-0">
|
|
||||||
<a
|
|
||||||
className="btn btn-primary text-nowrap text-white"
|
|
||||||
href={`https://coreui.io/react/docs/${props.href}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Explore Documentation
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
DocsComponents.propTypes = {
|
|
||||||
href: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DocsComponents
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
import React from 'react'
|
|
||||||
import { CNav, CNavItem, CNavLink, CTabContent, CTabPane } from '@coreui/react'
|
|
||||||
import CIcon from '@coreui/icons-react'
|
|
||||||
import { cilCode, cilMediaPlay } from '@coreui/icons'
|
|
||||||
|
|
||||||
const DocsExample = (props) => {
|
|
||||||
const { children, href, tabContentClassName } = props
|
|
||||||
|
|
||||||
const _href = `https://coreui.io/react/docs/${href}`
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="example">
|
|
||||||
<CNav variant="underline-border">
|
|
||||||
<CNavItem>
|
|
||||||
<CNavLink href="#" active>
|
|
||||||
<CIcon icon={cilMediaPlay} className="me-2" />
|
|
||||||
Preview
|
|
||||||
</CNavLink>
|
|
||||||
</CNavItem>
|
|
||||||
<CNavItem>
|
|
||||||
<CNavLink href={_href} target="_blank">
|
|
||||||
<CIcon icon={cilCode} className="me-2" />
|
|
||||||
Code
|
|
||||||
</CNavLink>
|
|
||||||
</CNavItem>
|
|
||||||
</CNav>
|
|
||||||
<CTabContent className={`rounded-bottom ${tabContentClassName ? tabContentClassName : ''}`}>
|
|
||||||
<CTabPane className="p-3 preview" visible>
|
|
||||||
{children}
|
|
||||||
</CTabPane>
|
|
||||||
</CTabContent>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DocsExample.propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
href: PropTypes.string,
|
|
||||||
tabContentClassName: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default React.memo(DocsExample)
|
|
||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
} from '@coreui/icons'
|
} from '@coreui/icons'
|
||||||
import CIcon from '@coreui/icons-react'
|
import CIcon from '@coreui/icons-react'
|
||||||
|
|
||||||
import avatar8 from './../../assets/images/avatars/8.jpg'
|
import avatar8 from 'src/assets/images/avatars/8.jpg'
|
||||||
|
|
||||||
const AppHeaderDropdown = () => {
|
const AppHeaderDropdown = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import AppHeaderDropdown from './AppHeaderDropdown'
|
import AppHeaderDropdown from 'src/components/header/AppHeaderDropdown'
|
||||||
|
|
||||||
export { AppHeaderDropdown }
|
export { AppHeaderDropdown }
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import AppBreadcrumb from './AppBreadcrumb'
|
import AppBreadcrumb from 'src/components/AppBreadcrumb'
|
||||||
import AppContent from './AppContent'
|
import AppContent from 'src/components/AppContent'
|
||||||
import AppFooter from './AppFooter'
|
import AppFooter from 'src/components/AppFooter'
|
||||||
import AppHeader from './AppHeader'
|
import AppHeader from 'src/components/AppHeader'
|
||||||
import AppHeaderDropdown from './header/AppHeaderDropdown'
|
import AppHeaderDropdown from 'src/components/header/AppHeaderDropdown'
|
||||||
import AppSidebar from './AppSidebar'
|
import AppSidebar from 'src/components/AppSidebar'
|
||||||
import DocsComponents from './DocsComponents'
|
import DocsIcons from 'src/components/DocsIcons'
|
||||||
import DocsIcons from './DocsIcons'
|
import DocsLink from 'src/components/DocsLink'
|
||||||
import DocsLink from './DocsLink'
|
|
||||||
import DocsExample from './DocsExample'
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AppBreadcrumb,
|
AppBreadcrumb,
|
||||||
@@ -16,8 +14,6 @@ export {
|
|||||||
AppHeader,
|
AppHeader,
|
||||||
AppHeaderDropdown,
|
AppHeaderDropdown,
|
||||||
AppSidebar,
|
AppSidebar,
|
||||||
DocsComponents,
|
|
||||||
DocsIcons,
|
DocsIcons,
|
||||||
DocsLink,
|
DocsLink,
|
||||||
DocsExample,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { createContext, useReducer, useEffect } from 'react';
|
import React, { createContext, useReducer, useEffect } from 'react';
|
||||||
|
|
||||||
import { getCurrentUser, isTokenValid } from 'src/axios/authService';
|
import { isTokenValid, getAccessTokenFromCookie, getUserFromToken } from 'src/axios/authService';
|
||||||
|
|
||||||
// 사용자 타입 정의
|
// 사용자 타입 정의
|
||||||
export interface Member {
|
export interface Member {
|
||||||
@@ -33,13 +33,16 @@ const initialState: AuthState = {
|
|||||||
error: null
|
error: null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Context 생성
|
// Context 타입 정의
|
||||||
export const AuthContext = createContext<{
|
export interface AuthContextType {
|
||||||
state: AuthState;
|
state: AuthState;
|
||||||
dispatch: React.Dispatch<AuthAction>;
|
dispatch: React.Dispatch<AuthAction>;
|
||||||
login: (token: string, member: Member) => void;
|
login: (token: string, member: Member) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
}>({
|
}
|
||||||
|
|
||||||
|
// Context 생성
|
||||||
|
export const AuthContext = createContext<AuthContextType>({
|
||||||
state: initialState,
|
state: initialState,
|
||||||
dispatch: () => null,
|
dispatch: () => null,
|
||||||
login: () => null,
|
login: () => null,
|
||||||
@@ -118,7 +121,8 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
// 초기 인증 상태 확인
|
// 초기 인증 상태 확인
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadUser = async () => {
|
const loadUser = async () => {
|
||||||
const token = localStorage.getItem('accessToken');
|
// localStorage 또는 Cookie에서 토큰 확인
|
||||||
|
let token = localStorage.getItem('accessToken') || getAccessTokenFromCookie();
|
||||||
|
|
||||||
if (!token || !isTokenValid(token)) {
|
if (!token || !isTokenValid(token)) {
|
||||||
dispatch({ type: 'LOGOUT' });
|
dispatch({ type: 'LOGOUT' });
|
||||||
@@ -126,8 +130,21 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const member = await getCurrentUser();
|
// 토큰이 쿠키에만 있고 localStorage에 없으면 저장해줌 (일관성 유지)
|
||||||
|
if (!localStorage.getItem('accessToken')) {
|
||||||
|
localStorage.setItem('accessToken', token);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedToken = getUserFromToken(token);
|
||||||
|
if (decodedToken) {
|
||||||
|
const member: Member = {
|
||||||
|
memberId: decodedToken.memberId,
|
||||||
|
memberName: decodedToken.memberName
|
||||||
|
};
|
||||||
dispatch({ type: 'MEMBER_LOADED', payload: member });
|
dispatch({ type: 'MEMBER_LOADED', payload: member });
|
||||||
|
} else {
|
||||||
|
dispatch({ type: 'AUTH_ERROR', payload: 'Invalid token' });
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch({ type: 'AUTH_ERROR', payload: 'Authentication failed' });
|
dispatch({ type: 'AUTH_ERROR', payload: 'Authentication failed' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { AuthContext } from '../context/AuthContext';
|
import { AuthContext, AuthContextType } from 'src/context/AuthContext';
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = (): AuthContextType => {
|
||||||
const context = useContext(AuthContext);
|
const context = useContext(AuthContext);
|
||||||
|
|
||||||
if (context === undefined) {
|
if (!context) {
|
||||||
throw new Error('useAuth must be used within an AuthProvider');
|
throw new Error('useAuth must be used within an AuthProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { createRoot } from 'react-dom/client'
|
|||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import 'core-js'
|
import 'core-js'
|
||||||
|
|
||||||
import App from './App'
|
import App from 'src/App'
|
||||||
import store from './store'
|
import store from 'src/store'
|
||||||
import { AuthProvider } from './context/AuthContext'
|
import { AuthProvider } from 'src/context/AuthContext'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { AppContent, AppSidebar, AppFooter, AppHeader } from '../components/index'
|
import { AppContent, AppSidebar, AppFooter, AppHeader } from 'src/components/index'
|
||||||
|
|
||||||
const DefaultLayout = () => {
|
const DefaultLayout = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
28
src/routes/ProtectedRoute.tsx
Normal file
28
src/routes/ProtectedRoute.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navigate, useLocation } from 'react-router-dom';
|
||||||
|
import { useAuth } from 'src/hooks/useAuth';
|
||||||
|
import { CSpinner } from '@coreui/react';
|
||||||
|
|
||||||
|
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const { state } = useAuth();
|
||||||
|
const { isAuthenticated, loading } = state;
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="pt-3 text-center">
|
||||||
|
<CSpinner color="primary" variant="grow" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
// 로그인 되지 않았다면 로그인 페이지로 리다이렉트
|
||||||
|
// 현재 위치를 state에 저장하여 로그인 후 다시 돌아올 수 있게 함
|
||||||
|
return <Navigate to="/login" state={{ from: location }} replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProtectedRoute;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const Dashboard = React.lazy(() => import('./views/dashboard/Dashboard'))
|
const Dashboard = React.lazy(() => import('src/views/dashboard/Dashboard'))
|
||||||
const Colors = React.lazy(() => import('./views/theme/colors/Colors'))
|
const Colors = React.lazy(() => import('src/views/theme/colors/Colors'))
|
||||||
const Typography = React.lazy(() => import('./views/theme/typography/Typography'))
|
const Typography = React.lazy(() => import('src/views/theme/typography/Typography'))
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/', exact: true, name: 'Home' },
|
{ path: '/', exact: true, name: 'Home' },
|
||||||
@@ -50,7 +50,7 @@ import avatar4 from 'src/assets/images/avatars/4.jpg'
|
|||||||
import avatar5 from 'src/assets/images/avatars/5.jpg'
|
import avatar5 from 'src/assets/images/avatars/5.jpg'
|
||||||
import avatar6 from 'src/assets/images/avatars/6.jpg'
|
import avatar6 from 'src/assets/images/avatars/6.jpg'
|
||||||
|
|
||||||
import MainChart from './MainChart'
|
import MainChart from 'src/views/dashboard/MainChart'
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const tableExample = [
|
const tableExample = [
|
||||||
|
|||||||
Reference in New Issue
Block a user