From 50b942801fa8d032009c6835f6e1495a7a70ad5d Mon Sep 17 00:00:00 2001 From: Andrii Arsenin Date: Thu, 9 Apr 2026 10:09:19 +0300 Subject: [PATCH] feat(client): add routing and sidebar icons - install react-router-dom and wrap app in HashRouter to avoid dev proxy conflicts - replace useState-based section switching with NavLink + Routes - add lucide icons (Globe, KeyRound, Monitor, ClipboardList) to nav items - icon opacity transitions on hover and active state via CSS --- client/package.json | 3 +- client/src/App.module.css | 16 ++++++++++- client/src/App.tsx | 51 +++++++++++++++++---------------- client/src/main.tsx | 5 +++- package-lock.json | 60 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 107 insertions(+), 28 deletions(-) diff --git a/client/package.json b/client/package.json index f96fc84..4abab3d 100644 --- a/client/package.json +++ b/client/package.json @@ -15,7 +15,8 @@ "lucide-react": "^1.7.0", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-i18next": "^17.0.2" + "react-i18next": "^17.0.2", + "react-router-dom": "^7.14.0" }, "devDependencies": { "@storybook/addon-a11y": "^10.3.5", diff --git a/client/src/App.module.css b/client/src/App.module.css index 6edddbe..52a644b 100644 --- a/client/src/App.module.css +++ b/client/src/App.module.css @@ -29,7 +29,9 @@ } .navItem { - display: block; + display: flex; + align-items: center; + gap: var(--space-2); width: 100%; padding: var(--space-2) var(--space-3); background: none; @@ -40,9 +42,21 @@ font-family: var(--font-family); text-align: left; cursor: pointer; + text-decoration: none; transition: background 120ms ease, color 120ms ease; } +.navIcon { + flex-shrink: 0; + opacity: 0.65; + transition: opacity 120ms ease; +} + +.navItem:hover .navIcon, +.navItemActive .navIcon { + opacity: 1; +} + .navItem:hover { background: var(--color-bg-subtle); } diff --git a/client/src/App.tsx b/client/src/App.tsx index 273b4a8..34b5ac2 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -4,24 +4,21 @@ import { EnvironmentsPage } from './pages/EnvironmentsPage'; import { KeysPage } from './pages/KeysPage'; import { SessionsPage } from './pages/SessionsPage'; import { ScenariosPage } from './pages/ScenariosPage'; -import { useState } from 'react'; +import { NavLink, Navigate, Route, Routes } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import { Globe, KeyRound, Monitor, ClipboardList } from 'lucide-react'; +import type { LucideIcon } from 'lucide-react'; -type Section = 'environments' | 'keys' | 'sessions' | 'scenarios'; - - +const NAV: { path: string; labelKey: string; Icon: LucideIcon }[] = [ + { path: '/environments', labelKey: 'nav.environments', Icon: Globe }, + { path: '/keys', labelKey: 'nav.keys', Icon: KeyRound }, + { path: '/sessions', labelKey: 'nav.sessions', Icon: Monitor }, + { path: '/scenarios', labelKey: 'nav.scenarios', Icon: ClipboardList }, +]; export default function App() { - const [active, setActive] = useState
('scenarios'); const { t } = useTranslation(); - const NAV: { id: Section; label: string }[] = [ - { id: 'environments', label: t('nav.environments') }, - { id: 'keys', label: t('nav.keys') }, - { id: 'sessions', label: t('nav.sessions') }, - { id: 'scenarios', label: t('nav.scenarios') }, - ]; - return (
- {active === 'environments' && } - {active === 'keys' && } - {active === 'sessions' && } - {active === 'scenarios' && } + + } /> + } /> + } /> + } /> + } /> + } /> +
); } - diff --git a/client/src/main.tsx b/client/src/main.tsx index 1556fa7..3334ef8 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,11 +1,14 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import { HashRouter } from 'react-router-dom' import './i18n' import './ui/tokens.css' import App from './App' createRoot(document.getElementById('root')!).render( - + + + , ) diff --git a/package-lock.json b/package-lock.json index 4a8478e..0f2a152 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,8 @@ "lucide-react": "^1.7.0", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-i18next": "^17.0.2" + "react-i18next": "^17.0.2", + "react-router-dom": "^7.14.0" }, "devDependencies": { "@storybook/addon-a11y": "^10.3.5", @@ -11083,6 +11084,57 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz", + "integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.0.tgz", + "integrity": "sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.14.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -11501,6 +11553,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",