diff --git a/src/admin/ui/AppShell.tsx b/src/admin/ui/AppShell.tsx
new file mode 100644
index 0000000..f73590f
--- /dev/null
+++ b/src/admin/ui/AppShell.tsx
@@ -0,0 +1,188 @@
+import { useState, useCallback } from "react";
+import { Outlet, Navigate, useLocation } from "react-router-dom";
+import { motion } from "framer-motion";
+import Box from "@mui/material/Box";
+import Drawer from "@mui/material/Drawer";
+import IconButton from "@mui/material/IconButton";
+import ScopedCssBaseline from "@mui/material/ScopedCssBaseline";
+import { useAuth } from "../context/AuthContext";
+import { useTheme as useAppTheme } from "../../context/ThemeContext";
+import { setLogoutAlert } from "../utils/api";
+import SidebarNav from "./SidebarNav";
+import ShortcutsHelp from "../components/ShortcutsHelp";
+
+const DRAWER_WIDTH = 248;
+
+export default function AppShell() {
+ const { isAuthenticated, loading, user, logout } = useAuth();
+ const { theme, toggleTheme } = useAppTheme();
+ const [mobileOpen, setMobileOpen] = useState(false);
+ const [loggingOut, setLoggingOut] = useState(false);
+ const location = useLocation();
+
+ const handleLogout = useCallback(() => {
+ setLoggingOut(true);
+ setMobileOpen(false);
+ setLogoutAlert();
+ setTimeout(() => logout(), 400);
+ }, [logout]);
+
+ if (loading) {
+ return (
+
+
+
+
+
+ );
+ }
+ if (!isAuthenticated) return ;
+ if (user?.require2FA && !user?.totpEnabled && location.pathname !== "/") {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+
+
+ setMobileOpen(false)}
+ ModalProps={{ keepMounted: true }}
+ sx={{
+ display: { xs: "block", md: "none" },
+ "& .MuiDrawer-paper": {
+ width: DRAWER_WIDTH,
+ boxSizing: "border-box",
+ },
+ }}
+ >
+ setMobileOpen(false)}
+ onLogout={handleLogout}
+ />
+
+
+
+
+ setMobileOpen(true)}
+ aria-label="Otevřít menu"
+ sx={{ display: { md: "none" } }}
+ >
+
+
+
+
+ {theme === "dark" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+}