diff --git a/src/admin/AdminApp.tsx b/src/admin/AdminApp.tsx
index 1414f4d..51c9d81 100644
--- a/src/admin/AdminApp.tsx
+++ b/src/admin/AdminApp.tsx
@@ -5,7 +5,7 @@ import { AuthProvider } from "./context/AuthContext";
import { AlertProvider } from "./context/AlertContext";
import { queryClient } from "./lib/queryClient";
import ErrorBoundary from "./components/ErrorBoundary";
-import AdminLayout from "./components/AdminLayout";
+import AppShell from "./ui/AppShell";
import AlertContainer from "./components/AlertContainer";
import MuiProvider from "./ui/MuiProvider";
import Login from "./pages/Login";
@@ -14,7 +14,6 @@ import "./variables.css";
import "./base.css";
import "./forms.css";
import "./buttons.css";
-import "./layout.css";
import "./components.css";
import "./tables.css";
import "./datepicker.css";
@@ -104,7 +103,7 @@ export default function AdminApp() {
{import.meta.env.DEV && UiKit && (
} />
)}
- }>
+ }>
} />
} />
} />
diff --git a/src/admin/components/AdminLayout.tsx b/src/admin/components/AdminLayout.tsx
deleted file mode 100644
index 11009a6..0000000
--- a/src/admin/components/AdminLayout.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { useState, useCallback } from "react";
-import { Outlet, Navigate, useLocation } from "react-router-dom";
-import { motion } from "framer-motion";
-import { useAuth } from "../context/AuthContext";
-import { useTheme } from "../../context/ThemeContext";
-import { setLogoutAlert } from "../utils/api";
-import useModalLock from "../hooks/useModalLock";
-import Sidebar from "./Sidebar";
-import ShortcutsHelp from "./ShortcutsHelp";
-
-export default function AdminLayout() {
- const { isAuthenticated, loading, user, logout } = useAuth();
- const { theme, toggleTheme } = useTheme();
- const [sidebarOpen, setSidebarOpen] = useState(false);
- const [loggingOut, setLoggingOut] = useState(false);
- const location = useLocation();
-
- // Session is managed by AuthProvider (initial check + proactive refresh via setTimeout).
- // Do not call checkSession on route changes — concurrent refresh calls with token rotation
- // would invalidate each other and kick the user out.
-
- const handleLogout = useCallback(() => {
- setLoggingOut(true);
- setSidebarOpen(false);
- setLogoutAlert();
- setTimeout(() => logout(), 400);
- }, [logout]);
-
- useModalLock(sidebarOpen);
-
- if (loading) {
- return (
-
- );
- }
-
- if (!isAuthenticated) {
- return ;
- }
-
- // If 2FA is required but user hasn't enabled it, redirect to dashboard (where setup lives)
- const needs2FASetup = user?.require2FA && !user?.totpEnabled;
- if (needs2FASetup && location.pathname !== "/") {
- return ;
- }
-
- return (
-
- setSidebarOpen(false)}
- onLogout={handleLogout}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/admin/components/Sidebar.tsx b/src/admin/components/Sidebar.tsx
deleted file mode 100644
index 86702c9..0000000
--- a/src/admin/components/Sidebar.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import { NavLink, useLocation } from "react-router-dom";
-import { useAuth } from "../context/AuthContext";
-import { useTheme } from "../../context/ThemeContext";
-import { menuSections, isItemActive, hasItemPermission } from "../ui/navData";
-
-interface SidebarProps {
- isOpen: boolean;
- onClose: () => void;
- onLogout: () => void;
-}
-
-export default function Sidebar({ isOpen, onClose, onLogout }: SidebarProps) {
- const { user, hasPermission } = useAuth();
- const { theme } = useTheme();
- const location = useLocation();
-
- const visibleSections = menuSections
- .map((section) => ({
- ...section,
- items: section.items.filter((item) =>
- hasItemPermission(item, hasPermission),
- ),
- }))
- .filter((section) => section.items.length > 0);
-
- return (
- <>
-
-
-
- >
- );
-}
diff --git a/src/admin/layout.css b/src/admin/layout.css
deleted file mode 100644
index cbda43a..0000000
--- a/src/admin/layout.css
+++ /dev/null
@@ -1,582 +0,0 @@
-/* ============================================================================
- Layout
- ============================================================================ */
-
-.admin-layout {
- display: flex;
- min-height: 100vh;
- min-height: 100dvh;
- background: var(--bg-primary);
-}
-
-@media (min-width: 1024px) {
- .admin-layout {
- align-items: flex-start;
- }
-}
-
-/* ============================================================================
- Sidebar
- ============================================================================ */
-
-/* -- Theme variables for sidebar -- */
-[data-theme="dark"] .admin-sidebar {
- --sb-bg: #141414;
- --sb-border: #2a2a2a;
- --sb-text: #a0a0a0;
- --sb-text-hover: #ddd;
- --sb-hover-bg: #1f1f1f;
- --sb-active-bg: #ffffff;
- --sb-active-text: #141414;
- --sb-label: #444;
- --sb-muted: #555;
- --sb-scrollbar: #333;
-}
-
-[data-theme="light"] .admin-sidebar {
- --sb-bg: #ffffff;
- --sb-border: #e8e6e1;
- --sb-text: #7c7c84;
- --sb-text-hover: #1a1a1a;
- --sb-hover-bg: #f5f4f2;
- --sb-active-bg: #141414;
- --sb-active-text: #ffffff;
- --sb-label: #a0a0a0;
- --sb-muted: #a0a0a0;
- --sb-scrollbar: #ddd;
-}
-
-.admin-sidebar {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- width: 100%;
- height: 100vh;
- height: 100dvh;
- z-index: 50;
- background: var(--sb-bg);
- border-right: 1px solid var(--sb-border);
- display: flex;
- flex-direction: column;
- padding-top: env(safe-area-inset-top, 0px);
- padding-bottom: env(safe-area-inset-bottom, 0px);
- padding-left: env(safe-area-inset-left, 0px);
- padding-right: env(safe-area-inset-right, 0px);
- transform: translateX(-100%);
- visibility: hidden;
- transition:
- transform 0.3s ease,
- visibility 0.3s ease;
- overflow: hidden;
- overscroll-behavior: none;
-}
-
-.admin-sidebar.open {
- transform: translateX(0);
- visibility: visible;
- touch-action: none;
-}
-
-@media (min-width: 1024px) {
- .admin-sidebar {
- right: auto;
- width: 220px;
- height: 100%;
- transform: none;
- visibility: visible;
- padding: 0;
- }
-}
-
-[data-theme="light"] .admin-sidebar {
- box-shadow:
- 1px 0 0 0 var(--sb-border),
- 4px 0 16px rgba(0, 0, 0, 0.04);
-}
-
-/* Sidebar Overlay (mobile) */
-.admin-sidebar-overlay {
- display: none;
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.5);
- z-index: 49;
- backdrop-filter: blur(2px);
-}
-
-.admin-sidebar-overlay.open {
- display: block;
-}
-
-@media (min-width: 1024px) {
- .admin-sidebar-overlay {
- display: none !important;
- }
-}
-
-/* Sidebar Header */
-.admin-sidebar-header {
- padding: 0 18px;
- height: 73px;
- border-bottom: 1px solid var(--sb-border);
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-shrink: 0;
-}
-
-.admin-sidebar-logo {
- height: 28px;
- width: auto;
-}
-
-.admin-sidebar-close {
- display: block;
- padding: 0.5rem;
- background: transparent;
- border: none;
- color: var(--sb-text);
- cursor: pointer;
- border-radius: 6px;
-}
-
-.admin-sidebar-close:hover {
- background: var(--sb-hover-bg);
- color: var(--sb-text-hover);
-}
-
-@media (min-width: 1024px) {
- .admin-sidebar-close {
- display: none;
- }
-}
-
-/* Sidebar Navigation */
-.admin-sidebar-nav {
- flex: 1;
- min-height: 0;
- overflow-y: auto;
- -webkit-overflow-scrolling: touch;
- overscroll-behavior: contain;
- padding: 4px 0;
-}
-
-.admin-sidebar-nav::-webkit-scrollbar {
- width: 5px;
-}
-
-.admin-sidebar-nav::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.admin-sidebar-nav::-webkit-scrollbar-thumb {
- background: var(--sb-scrollbar);
- border-radius: 99px;
-}
-
-/* Nav Section */
-.admin-nav-section {
- padding: 14px 10px 6px;
-}
-
-.admin-nav-label {
- font-size: 10px;
- font-weight: 600;
- letter-spacing: 1px;
- text-transform: uppercase;
- color: var(--sb-label);
- padding: 0 8px;
- margin-bottom: 4px;
-}
-
-/* Nav Item */
-.admin-nav-item {
- display: flex;
- align-items: center;
- gap: 9px;
- padding: 7px 10px;
- border-radius: 7px;
- color: var(--sb-text);
- cursor: pointer;
- transition: all 0.15s ease;
- font-size: 13px;
- font-weight: 450;
- margin-bottom: 1px;
- text-decoration: none;
- user-select: none;
-}
-
-.admin-nav-item:hover {
- background: var(--sb-hover-bg);
- color: var(--sb-text-hover);
-}
-
-.admin-nav-item.active {
- background: var(--sb-active-bg);
- color: var(--sb-active-text);
- font-weight: 600;
-}
-
-.admin-nav-item.active svg {
- color: var(--accent-color);
-}
-
-.admin-nav-item svg {
- width: 16px;
- height: 16px;
- flex-shrink: 0;
-}
-
-@media (max-width: 1023px) {
- .admin-nav-item {
- padding: 10px 12px;
- font-size: 15px;
- gap: 10px;
- }
-
- .admin-nav-item svg {
- width: 18px;
- height: 18px;
- }
-}
-
-/* Sidebar Footer */
-.admin-sidebar-footer {
- margin-top: auto;
- border-top: 1px solid var(--sb-border);
- padding: 14px 10px;
- flex-shrink: 0;
-}
-
-.admin-user-chip {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 8px;
- border-radius: 8px;
- margin-bottom: 4px;
-}
-
-.admin-user-avatar {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- background: var(--accent-color);
- color: #fff;
- display: flex;
- align-items: center;
- justify-content: center;
- font-weight: 700;
- font-size: 12px;
- flex-shrink: 0;
-}
-
-.admin-user-details {
- flex: 1;
- min-width: 0;
-}
-
-.admin-user-name {
- color: var(--sb-text-hover);
- font-size: 13px;
- font-weight: 500;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.admin-user-role {
- color: var(--sb-muted);
- font-size: 11px;
-}
-
-.admin-logout-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- width: 100%;
- padding: 7px 10px;
- background: transparent;
- border: none;
- color: var(--sb-text);
- cursor: pointer;
- border-radius: 7px;
- font-size: 12px;
- font-family: inherit;
- transition: all 0.15s ease;
-}
-
-.admin-logout-btn:hover {
- background: var(--sb-hover-bg);
- color: var(--sb-text-hover);
-}
-
-.admin-logout-btn svg {
- width: 16px;
- height: 16px;
- flex-shrink: 0;
-}
-
-@media (max-width: 480px) {
- .admin-sidebar-footer {
- padding: 10px 8px;
- }
-
- .admin-user-chip {
- padding: 6px;
- }
-
- .admin-logout-btn {
- padding: 8px;
- font-size: 13px;
- }
-}
-
-/* ============================================================================
- Main Content Area
- ============================================================================ */
-
-.admin-main {
- flex: 1;
- min-width: 0;
- min-height: 100vh;
- min-height: 100dvh;
- display: flex;
- flex-direction: column;
-}
-
-@media (min-width: 1024px) {
- .admin-main {
- margin-left: 220px;
- }
-}
-
-/* Header */
-.admin-header {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- z-index: 30;
- height: calc(73px + env(safe-area-inset-top, 0px));
- background: var(--bg-secondary);
- border-bottom: 1px solid var(--border-color);
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 1rem;
- padding-top: env(safe-area-inset-top, 0px);
-}
-
-@media (min-width: 1024px) {
- .admin-header {
- left: 220px;
- padding: 0 1.5rem;
- }
-}
-
-.admin-menu-btn {
- display: block;
- padding: 0.5rem;
- background: transparent;
- border: none;
- color: var(--text-secondary);
- cursor: pointer;
- border-radius: var(--border-radius-sm);
-}
-
-.admin-menu-btn:hover {
- background: var(--bg-tertiary);
-}
-
-@media (min-width: 1024px) {
- .admin-menu-btn {
- display: none;
- }
-}
-
-.admin-header-theme-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- padding: 0;
- background: var(--bg-tertiary);
- border: 1px solid var(--border-color);
- color: var(--text-secondary);
- cursor: pointer;
- border-radius: 50%;
- transition: var(--transition);
- position: relative;
- overflow: hidden;
-}
-
-.admin-header-theme-btn:hover {
- background: var(--bg-tertiary);
- border-color: var(--border-color-hover);
- transform: scale(1.05);
-}
-
-.admin-theme-icon {
- position: absolute;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--text-primary);
- transition: all 0.3s ease;
- opacity: 0;
- transform: rotate(180deg) scale(0.5);
-}
-
-.admin-theme-icon.visible {
- opacity: 1;
- transform: rotate(0) scale(1);
-}
-
-/* Content */
-.admin-content {
- flex: 1;
- padding: 1rem;
- padding-top: calc(73px + 1rem + env(safe-area-inset-top, 0px));
-}
-
-@media (min-width: 1024px) {
- .admin-content {
- padding: 28px 32px;
- padding-top: calc(73px + 28px);
- }
-}
-
-/* ============================================================================
- Page Headers
- ============================================================================ */
-
-.admin-page-header {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- margin-bottom: 1rem;
-}
-
-@media (min-width: 640px) {
- .admin-page-header {
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- }
-}
-
-.admin-page-title {
- font-size: 22px;
- font-weight: 700;
- color: var(--text-primary);
- font-family: var(--font-heading);
- margin-bottom: 0.25rem;
-}
-
-.admin-page-subtitle {
- color: var(--text-secondary);
- font-size: 13px;
-}
-
-.admin-page-actions {
- display: flex;
- gap: 0.75rem;
- flex-wrap: wrap;
-}
-
-@media (max-width: 640px) {
- .admin-page-actions {
- width: 100%;
- }
-
- .admin-page-actions .admin-btn {
- flex: 1;
- }
-}
-
-/* ============================================================================
- Grid System
- ============================================================================ */
-
-.admin-grid {
- display: grid;
- gap: 1rem;
-}
-
-.admin-grid > .admin-card {
- margin-bottom: 0;
-}
-
-.admin-grid-3 {
- grid-template-columns: 1fr;
-}
-
-@media (min-width: 640px) {
- .admin-grid-3 {
- grid-template-columns: repeat(2, 1fr);
- }
-}
-
-@media (min-width: 1024px) {
- .admin-grid-3 {
- grid-template-columns: repeat(3, 1fr);
- }
-}
-
-.admin-grid-4 {
- grid-template-columns: repeat(2, 1fr);
-}
-
-@media (min-width: 768px) {
- .admin-grid-4 {
- grid-template-columns: repeat(4, 1fr);
- }
-}
-
-/* Page header on mobile */
-@media (max-width: 480px) {
- .admin-page-title {
- font-size: 18px;
- }
-
- .admin-page-subtitle {
- font-size: 12px;
- }
-
- .admin-content {
- padding: 12px !important;
- padding-top: calc(73px + 12px + env(safe-area-inset-top, 0px)) !important;
- }
-}
-
-/* Grid - single column on small mobile */
-@media (max-width: 480px) {
- .admin-grid-4 {
- grid-template-columns: 1fr;
- }
-}
-
-/* ============================================================================
- Settings Grid
- ============================================================================ */
-
-.admin-settings-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 1.5rem;
-}
-
-.admin-settings-grid > .admin-card {
- margin-bottom: 0;
-}
-
-@media (max-width: 900px) {
- .admin-settings-grid {
- grid-template-columns: 1fr;
- }
-}