From 7997773129ad9544a431cff108d356de6eba9015 Mon Sep 17 00:00:00 2001 From: nessi Date: Thu, 12 Feb 2026 10:07:22 +0100 Subject: [PATCH] Revamp navigation and styling; enhance API URL handling. Replaced `Link` components with `NavLink` for active state support and added new sidebar navigation styling. Enhanced API URL handling to prevent mixed content when using HTTPS. Updated layout and CSS for better responsiveness and consistent design. --- .env.example | 3 +- frontend/src/App.jsx | 30 +++++++++++++---- frontend/src/api.js | 19 ++++++++++- frontend/src/styles.css | 75 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 116 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 1ffd1a5..f85269e 100644 --- a/.env.example +++ b/.env.example @@ -25,4 +25,5 @@ INIT_ADMIN_PASSWORD=ChangeMe123! # Frontend FRONTEND_PORT=5173 -VITE_API_URL=http://localhost:8000/api/v1 +# For reverse proxy + SSL prefer relative path to avoid mixed-content. +VITE_API_URL=/api/v1 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 51d539c..bce6bd1 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { Link, Navigate, Route, Routes, useLocation } from "react-router-dom"; +import { NavLink, Navigate, Route, Routes, useLocation } from "react-router-dom"; import { useAuth } from "./state"; import { LoginPage } from "./pages/LoginPage"; import { DashboardPage } from "./pages/DashboardPage"; @@ -17,20 +17,36 @@ function Protected({ children }) { function Layout({ children }) { const { me, logout } = useAuth(); + const navClass = ({ isActive }) => `nav-btn${isActive ? " active" : ""}`; + return (
{children}
diff --git a/frontend/src/api.js b/frontend/src/api.js index a97e22f..5ee9bfb 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1,4 +1,21 @@ -const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8000/api/v1"; +function resolveApiUrl() { + const raw = (import.meta.env.VITE_API_URL || "").trim(); + const fallback = "/api/v1"; + if (!raw) return fallback; + + try { + const parsed = new URL(raw, window.location.origin); + if (window.location.protocol === "https:" && parsed.protocol === "http:") { + // Avoid mixed-content when UI is served over HTTPS. + parsed.protocol = "https:"; + } + return parsed.toString().replace(/\/$/, ""); + } catch { + return fallback; + } +} + +const API_URL = resolveApiUrl(); export async function apiFetch(path, options = {}, tokens, onUnauthorized) { const headers = { diff --git a/frontend/src/styles.css b/frontend/src/styles.css index e93b982..10061c6 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -17,6 +17,7 @@ body { font-family: "Space Grotesk", "Segoe UI", sans-serif; color: var(--text); background: radial-gradient(circle at top right, #1d335f, #0b1020 55%); + overflow: hidden; } a { @@ -27,7 +28,8 @@ a { .shell { display: grid; grid-template-columns: 260px 1fr; - min-height: 100vh; + height: 100vh; + overflow: hidden; } .sidebar { @@ -37,14 +39,60 @@ a { display: flex; flex-direction: column; gap: 20px; + height: 100vh; + position: sticky; + top: 0; + overflow: hidden; } -.sidebar nav { +.sidebar-nav { display: flex; flex-direction: column; gap: 10px; } +.nav-btn { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + border: 1px solid #223056; + border-radius: 10px; + background: linear-gradient(180deg, #101b3a, #0d1530); + color: #c6d5ef; + transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease; +} + +.nav-btn:hover { + border-color: #2e4f98; + background: linear-gradient(180deg, #13224b, #101b3a); + transform: translateY(-1px); +} + +.nav-btn.active { + border-color: #38bdf8; + box-shadow: inset 0 0 0 1px #38bdf860; + background: linear-gradient(180deg, #16305f, #101f43); + color: #ecf5ff; +} + +.nav-icon { + width: 26px; + height: 26px; + border-radius: 8px; + display: grid; + place-items: center; + font-size: 11px; + font-weight: 700; + border: 1px solid #2b3f74; + background: #0d1631; +} + +.nav-label { + font-size: 15px; + font-weight: 600; +} + .profile { margin-top: auto; border-top: 1px solid #223056; @@ -58,6 +106,8 @@ a { .main { padding: 24px; + height: 100vh; + overflow-y: auto; } .card { @@ -120,6 +170,17 @@ button { cursor: pointer; } +.logout-btn { + width: 100%; + margin-top: 8px; + border-color: #374f8f; + background: linear-gradient(180deg, #13224b, #101b3a); +} + +.logout-btn:hover { + border-color: #38bdf8; +} + table { width: 100%; border-collapse: collapse; @@ -215,16 +276,26 @@ td { } @media (max-width: 980px) { + body { + overflow: auto; + } .shell { grid-template-columns: 1fr; + height: auto; + overflow: visible; } .sidebar { position: sticky; top: 0; z-index: 2; + height: auto; } .grid.two, .grid.three { grid-template-columns: 1fr; } + .main { + height: auto; + overflow: visible; + } }