From d4aa982e1a7febf4680f5f63de3e62cf567bde65 Mon Sep 17 00:00:00 2001 From: HYOJIN Date: Wed, 8 Apr 2026 16:54:29 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(ui):=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20-=20=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C,?= =?UTF-8?q?=20API=20Key=20UX,=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 공통: - 다크/라이트 모드 (ThemeContext, Tailwind dark variant, 전체 페이지 적용) - 사이드바 아이콘 링크체인 (#FF2E63), 헤더/사이드바 높이 통일 - 컨텐츠 영역 max-w-7xl 마진 통일 (대시보드 제외) - 전체 Actions 버튼 bg-color-100 스타일 통일 - date input 달력 아이콘 다크모드 (filter invert) API Keys: - Request: 영구 사용 옵션 추가, 프리셋/영구 버튼 다크모드 - My Keys: ADMIN 직접 생성 제거 → Request 페이지 정식 폼으로 통일 - Admin: 키 관리 만료일 컬럼 추가, 권한 편집 제거 (승인 단계에서만 가능) Gateway: - API 경로 {변수} 패턴 매칭 지원 Closes #15 Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/App.tsx | 3 + frontend/src/components/ProtectedRoute.tsx | 4 +- frontend/src/hooks/useTheme.ts | 8 + frontend/src/index.css | 7 + frontend/src/layouts/AuthLayout.tsx | 2 +- frontend/src/layouts/MainLayout.tsx | 31 +- frontend/src/pages/DashboardPage.tsx | 128 +++---- frontend/src/pages/LoginPage.tsx | 12 +- frontend/src/pages/NotFoundPage.tsx | 4 +- frontend/src/pages/admin/ServicesPage.tsx | 142 ++++---- frontend/src/pages/admin/TenantsPage.tsx | 66 ++-- frontend/src/pages/admin/UsersPage.tsx | 86 ++--- frontend/src/pages/apikeys/KeyAdminPage.tsx | 318 ++++++------------ frontend/src/pages/apikeys/KeyRequestPage.tsx | 162 +++++---- frontend/src/pages/apikeys/MyKeysPage.tsx | 160 +++------ .../pages/monitoring/RequestLogDetailPage.tsx | 60 ++-- .../src/pages/monitoring/RequestLogsPage.tsx | 80 ++--- .../monitoring/ServiceStatusDetailPage.tsx | 52 +-- .../pages/monitoring/ServiceStatusPage.tsx | 26 +- frontend/src/services/apiKeyService.ts | 3 - frontend/src/store/ThemeContext.tsx | 48 +++ .../gateway/service/GatewayService.java | 38 ++- 22 files changed, 695 insertions(+), 745 deletions(-) create mode 100644 frontend/src/hooks/useTheme.ts create mode 100644 frontend/src/store/ThemeContext.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8ce7397..3f4f463 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,5 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import ThemeProvider from './store/ThemeContext'; import AuthProvider from './store/AuthContext'; import AuthLayout from './layouts/AuthLayout'; import MainLayout from './layouts/MainLayout'; @@ -22,6 +23,7 @@ const BASE_PATH = '/snp-connection'; const App = () => { return ( + }> @@ -47,6 +49,7 @@ const App = () => { + ); }; diff --git a/frontend/src/components/ProtectedRoute.tsx b/frontend/src/components/ProtectedRoute.tsx index 6a3d913..e2e53b0 100644 --- a/frontend/src/components/ProtectedRoute.tsx +++ b/frontend/src/components/ProtectedRoute.tsx @@ -6,8 +6,8 @@ const ProtectedRoute = () => { if (isLoading) { return ( -
-
+
+
); } diff --git a/frontend/src/hooks/useTheme.ts b/frontend/src/hooks/useTheme.ts new file mode 100644 index 0000000..6e59a54 --- /dev/null +++ b/frontend/src/hooks/useTheme.ts @@ -0,0 +1,8 @@ +import { useContext } from 'react'; +import { ThemeContext } from '../store/ThemeContext'; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) throw new Error('useTheme must be used within ThemeProvider'); + return context; +}; diff --git a/frontend/src/index.css b/frontend/src/index.css index d4b5078..3f570eb 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1 +1,8 @@ @import 'tailwindcss'; + +@custom-variant dark (&:where(.dark, .dark *)); + +/* Dark mode date input calendar icon */ +.dark input[type="date"]::-webkit-calendar-picker-indicator { + filter: invert(1); +} diff --git a/frontend/src/layouts/AuthLayout.tsx b/frontend/src/layouts/AuthLayout.tsx index 6819e6a..5b658cd 100644 --- a/frontend/src/layouts/AuthLayout.tsx +++ b/frontend/src/layouts/AuthLayout.tsx @@ -2,7 +2,7 @@ import { Outlet } from 'react-router-dom'; const AuthLayout = () => { return ( -
+
); diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index 2930151..d29c28e 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { Outlet, NavLink } from 'react-router-dom'; import { useAuth } from '../hooks/useAuth'; +import { useTheme } from '../hooks/useTheme'; interface NavGroup { label: string; @@ -37,6 +38,7 @@ const navGroups: NavGroup[] = [ const MainLayout = () => { const { user, logout } = useAuth(); + const { theme, toggleTheme } = useTheme(); const [openGroups, setOpenGroups] = useState>({ Monitoring: true, 'API Keys': true, @@ -57,9 +59,9 @@ const MainLayout = () => {
{/* Sidebar */}