From c0d224ef212917834f9a3ff2bf4525adec2c1c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Thu, 18 Jun 2026 13:12:23 +0900 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20(=EB=A1=9C=EA=B7=B8=EC=9D=B8/=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83/=EB=82=B4=EC=A0=95=EB=B3=B4/?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=8A=B8=20=EA=B0=80=EB=93=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인 실연동: 성공 시 access/refresh 토큰 저장, me 캐시 무효화 - SidebarFooter: useMeQuery 로 공급사명 표시, useLogoutMutation 로 로그아웃 - RequireAuth 가드로 /list·/chat 보호, 토큰 없으면 로그인으로 - 인증 만료/폐기 시 로그인 페이지로 이동(setUnauthorizedHandler) - LoginPage: 이미 로그인 상태면 목록으로 리다이렉트 Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/App.tsx | 25 +++++++++++++++++-- .../features/auth/components/RequireAuth.tsx | 12 +++++++++ .../auth/components/SidebarFooter.tsx | 18 ++++++++++--- .../features/auth/hooks/useLoginMutation.ts | 15 ++--------- frontend/src/features/auth/index.ts | 1 + frontend/src/pages/LoginPage.tsx | 7 ++++++ 6 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 frontend/src/features/auth/components/RequireAuth.tsx 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 (