diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 363b765..a864750 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,14 +1,35 @@
import { createBrowserRouter, RouterProvider } from 'react-router'
+import { setUnauthorizedHandler } from '@/apis'
+import { RequireAuth } from '@/features/auth'
import LoginPage from '@/pages/LoginPage'
import ListPage from '@/pages/ListPage'
import ChatPage from '@/pages/ChatPage'
const router = createBrowserRouter([
{ path: '/', element: },
- { path: '/list', element: },
- { path: '/chat', element: },
+ {
+ path: '/list',
+ element: (
+
+
+
+ ),
+ },
+ {
+ path: '/chat',
+ element: (
+
+
+
+ ),
+ },
])
+// 토큰 만료/폐기로 인증이 끊기면 로그인 페이지로 이동시킨다.
+setUnauthorizedHandler(() => {
+ void router.navigate('/')
+})
+
function App() {
return
}
diff --git a/frontend/src/features/auth/components/RequireAuth.tsx b/frontend/src/features/auth/components/RequireAuth.tsx
new file mode 100644
index 0000000..fc4608c
--- /dev/null
+++ b/frontend/src/features/auth/components/RequireAuth.tsx
@@ -0,0 +1,12 @@
+import type { ReactNode } from 'react'
+import { Navigate } from 'react-router'
+import { tokenStorage } from '@/apis'
+
+// 토큰이 없으면 로그인 페이지로 보낸다 (인증 영역 가드).
+// 토큰이 있으나 만료/폐기된 경우는 요청 시 인터셉터가 세션을 종료시킨다.
+export function RequireAuth({ children }: { children: ReactNode }) {
+ if (!tokenStorage.hasToken()) {
+ return
+ }
+ return <>{children}>
+}
diff --git a/frontend/src/features/auth/components/SidebarFooter.tsx b/frontend/src/features/auth/components/SidebarFooter.tsx
index aeca1d5..eda61dc 100644
--- a/frontend/src/features/auth/components/SidebarFooter.tsx
+++ b/frontend/src/features/auth/components/SidebarFooter.tsx
@@ -1,18 +1,30 @@
+import { useNavigate } from 'react-router'
+import { useLogoutMutation, useMeQuery } from '@/apis'
import { Button } from '@/components'
// 사이드바 하단: 공급사명 + 로그아웃
export function SidebarFooter() {
+ const navigate = useNavigate()
+ const { data: user } = useMeQuery()
+ const logout = useLogoutMutation()
+
+ const handleLogout = () => {
+ logout.mutate(undefined, {
+ onSuccess: () => navigate('/'),
+ })
+ }
+
return (
- {/* TODO: 공급사명 (auth store 연동) */}
- -
+ {user?.supplierName ?? '-'}
diff --git a/frontend/src/features/auth/hooks/useLoginMutation.ts b/frontend/src/features/auth/hooks/useLoginMutation.ts
index 3295d41..76fbeee 100644
--- a/frontend/src/features/auth/hooks/useLoginMutation.ts
+++ b/frontend/src/features/auth/hooks/useLoginMutation.ts
@@ -1,13 +1,2 @@
-import { useMutation } from '@tanstack/react-query'
-
-export interface LoginParams {
- id: string
- password: string
-}
-
-// 임시 stub (검증만 통과하면 성공). TODO: 로그인 API 연동
-export function useLoginMutation() {
- return useMutation
({
- mutationFn: async () => {},
- })
-}
+// 실제 로그인 API 연동은 apis/auth 로 이전됨. 기존 import 경로 호환을 위해 재노출한다.
+export { useLoginMutation, type LoginParams } from '@/apis/auth'
diff --git a/frontend/src/features/auth/index.ts b/frontend/src/features/auth/index.ts
index 2b91506..e940c43 100644
--- a/frontend/src/features/auth/index.ts
+++ b/frontend/src/features/auth/index.ts
@@ -1,2 +1,3 @@
export { LoginForm } from '@/features/auth/components/LoginForm'
export { SidebarFooter } from '@/features/auth/components/SidebarFooter'
+export { RequireAuth } from '@/features/auth/components/RequireAuth'
diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx
index 8543179..dc36dbb 100644
--- a/frontend/src/pages/LoginPage.tsx
+++ b/frontend/src/pages/LoginPage.tsx
@@ -1,7 +1,14 @@
+import { Navigate } from 'react-router'
+import { tokenStorage } from '@/apis'
import { Logo } from '@/components'
import { LoginForm } from '@/features/auth'
export function LoginPage() {
+ // 이미 로그인된 상태면 목록으로
+ if (tokenStorage.hasToken()) {
+ return
+ }
+
return (