From 77cf0ab69fcee2a2005a8379aaa8be6776a78a32 Mon Sep 17 00:00:00 2001 From: Tautvydas Derzinskas Date: Sun, 13 Sep 2026 11:41:58 +0200 Subject: [PATCH] feat: admin menu updates --- README.md | 24 +-- frontend/src/App.tsx | 5 + frontend/src/components/Layout/AppLayout.tsx | 15 +- .../src/components/Layout/Sidebar/index.tsx | 140 +++--------------- frontend/src/pages/AdminPage/index.tsx | 63 ++++++++ .../src/pages/AdminSettingsPage/index.tsx | 2 +- 6 files changed, 114 insertions(+), 135 deletions(-) create mode 100644 frontend/src/pages/AdminPage/index.tsx diff --git a/README.md b/README.md index 07a60fc..9ffedcb 100644 --- a/README.md +++ b/README.md @@ -50,24 +50,24 @@ Instead of having your collection scattered across different websites and your f - - - + + + - - - + + + - - - + + + - - - + + +
Dashboard
Dashboard
Models
Models
Model details
Model Details
Dashboard
Dashboard
Models
Models
Model details
Model Details
3D preview
3D Preview
Collections
Collections
Tags
Tags
3D preview
3D Preview
Collections
Collections
Tags
Tags
Downloads
Downloads
My models
My Models
Dark theme
Dark Theme
Downloads
Downloads
My models
My Models
Dark theme
Dark Theme
Thingport Grab on Printables
Thingport Grab — Printables
Thingport Grab on Thingiverse
Thingport Grab — Thingiverse
Thingport Grab on MakerWorld
Thingport Grab — MakerWorld
Thingport Grab on Printables
Thingport Grab — Printables
Thingport Grab on Thingiverse
Thingport Grab — Thingiverse
Thingport Grab on MakerWorld
Thingport Grab — MakerWorld
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cce0384..9c963f9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -21,6 +21,7 @@ import ProfilePage from "./pages/ProfilePage"; import ChangeEmailPage from "./pages/ProfilePage/ChangeEmailPage"; import ChangePasswordPage from "./pages/ProfilePage/ChangePasswordPage"; import DownloadPage from "./pages/DownloadPage"; +import AdminPage from "./pages/AdminPage"; import AdminSettingsPage from "./pages/AdminSettingsPage"; import UsersPage from "./pages/UsersPage"; import LogsPage from "./pages/LogsPage"; @@ -181,6 +182,10 @@ function AppShell({ element={} /> } /> + : } + /> navigate("/"); + } else if (path === "/admin") { + // The Administration hub itself -- a top-level page reachable directly from the sidebar, + // same as Models/Collections/Tags/Downloads, so its back button goes to the Dashboard. + title = t("sidebar.administration"); + onBack = () => navigate("/"); } else if (path.startsWith("/admin-settings")) { title = t("adminSettings.pageTitle"); - onBack = () => navigate("/"); + onBack = () => navigate("/admin"); } else if (path.startsWith("/admin-users")) { title = t("adminSettings.users.heading"); - onBack = () => navigate("/"); + onBack = () => navigate("/admin"); } else if (path.startsWith("/admin-logs")) { title = t("adminSettings.logs.heading"); - onBack = () => navigate("/"); + onBack = () => navigate("/admin"); } else if (path.startsWith("/admin-triggers")) { title = t("adminSettings.triggers.heading"); - onBack = () => navigate("/"); + onBack = () => navigate("/admin"); } else if (path.startsWith("/admin-connections")) { title = t("adminSettings.connections.heading"); - onBack = () => navigate("/"); + onBack = () => navigate("/admin"); } return { title, onBack }; diff --git a/frontend/src/components/Layout/Sidebar/index.tsx b/frontend/src/components/Layout/Sidebar/index.tsx index f909b81..f65c094 100644 --- a/frontend/src/components/Layout/Sidebar/index.tsx +++ b/frontend/src/components/Layout/Sidebar/index.tsx @@ -12,7 +12,6 @@ import List from "@mui/material/List"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; -import Collapse from "@mui/material/Collapse"; import Divider from "@mui/material/Divider"; import Tooltip from "@mui/material/Tooltip"; import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard"; @@ -22,13 +21,6 @@ import LocalOfferIcon from "@mui/icons-material/LocalOffer"; import BookmarkIcon from "@mui/icons-material/Bookmark"; import DownloadIcon from "@mui/icons-material/Download"; import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettings"; -import SettingsIcon from "@mui/icons-material/Settings"; -import PeopleIcon from "@mui/icons-material/People"; -import HistoryIcon from "@mui/icons-material/History"; -import BoltIcon from "@mui/icons-material/Bolt"; -import CableIcon from "@mui/icons-material/Cable"; -import ExpandLessIcon from "@mui/icons-material/ExpandLess"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import Wordmark from "../../Wordmark"; @@ -100,8 +92,9 @@ type Props = { /** The persistent app-wide navigation rail: Dashboard, Models, Collections, Tags, Downloads, then * (once any tag is bookmarked) a divider, a "Bookmarks" heading, and one row per bookmarked tag, - * and finally (for admins) Administration. Category browsing lives inside the Models page - * itself, not here. */ + * and finally (for admins) a single "Administration" row -- it's just a link to the /admin hub + * page now, not an expandable list of every admin sub-page (see AdminPage). Category browsing + * lives inside the Models page itself, not here. */ export default function Sidebar({ isAdmin, onSelectCategory, tagBookmarksVersion }: Props) { const { t } = useTranslation(["app", "common"]); const location = useLocation(); @@ -110,7 +103,6 @@ export default function Sidebar({ isAdmin, onSelectCategory, tagBookmarksVersion if (typeof window === "undefined") return false; return window.localStorage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY) === "true"; }); - const [adminExpanded, setAdminExpanded] = useState(false); const [bookmarkedTags, setBookmarkedTags] = useState([]); useEffect(() => { @@ -128,13 +120,10 @@ export default function Sidebar({ isAdmin, onSelectCategory, tagBookmarksVersion const onTags = location.pathname.startsWith("/models/tags"); const onModels = (location.pathname.startsWith("/models") && !onCollections && !onTags) || location.pathname.startsWith("/authors"); const onDownload = location.pathname.startsWith("/downloads"); - const onAdminSettings = location.pathname.startsWith("/admin-settings"); - const onAdminUsers = location.pathname.startsWith("/admin-users"); - const onAdminLogs = location.pathname.startsWith("/admin-logs"); - const onAdminTriggers = location.pathname.startsWith("/admin-triggers"); - const onAdminConnections = location.pathname.startsWith("/admin-connections"); - const onAdmin = onAdminSettings || onAdminUsers || onAdminLogs || onAdminTriggers || onAdminConnections; - const adminOpen = adminExpanded || onAdmin; + // Covers both the hub itself (/admin) and every sub-page (/admin-settings, /admin-users, ...) + // as a plain string-prefix match -- they're only ever reached from that hub now, not listed + // individually here any more, so one flag is all this row needs. + const onAdmin = location.pathname.startsWith("/admin"); const toggleCollapsed = () => { setCollapsed(prev => { @@ -344,106 +333,23 @@ export default function Sidebar({ isAdmin, onSelectCategory, tagBookmarksVersion <> {collapsed ? ( - <> - } - label={t("common:settings")} - selected={onAdminSettings} - onClick={() => navigate("/admin-settings")} - /> - } - label={t("adminSettings.users.heading")} - selected={onAdminUsers} - onClick={() => navigate("/admin-users")} - /> - } - label={t("adminSettings.logs.heading")} - selected={onAdminLogs} - onClick={() => navigate("/admin-logs")} - /> - } - label={t("adminSettings.triggers.heading")} - selected={onAdminTriggers} - onClick={() => navigate("/admin-triggers")} - /> - } - label={t("adminSettings.connections.heading")} - selected={onAdminConnections} - onClick={() => navigate("/admin-connections")} - /> - + } + label={t("sidebar.administration")} + selected={onAdmin} + onClick={() => navigate("/admin")} + /> ) : ( - <> - setAdminExpanded(v => !v)} - sx={{ borderRadius: 1, mb: 0.5, ...navRowSx(onAdmin) }} - > - - - - - {adminOpen ? : } - - - - navigate("/admin-settings")} - sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminSettings) }} - > - - - - - - navigate("/admin-users")} - sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminUsers) }} - > - - - - - - navigate("/admin-logs")} - sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminLogs) }} - > - - - - - - navigate("/admin-triggers")} - sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminTriggers) }} - > - - - - - - navigate("/admin-connections")} - sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminConnections) }} - > - - - - - - - - + navigate("/admin")} + sx={{ borderRadius: 1, mb: 0.5, ...navRowSx(onAdmin) }} + > + + + + + )} )} diff --git a/frontend/src/pages/AdminPage/index.tsx b/frontend/src/pages/AdminPage/index.tsx new file mode 100644 index 0000000..ad251b9 --- /dev/null +++ b/frontend/src/pages/AdminPage/index.tsx @@ -0,0 +1,63 @@ +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import Paper from "@mui/material/Paper"; +import List from "@mui/material/List"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; +import SettingsIcon from "@mui/icons-material/Settings"; +import PeopleIcon from "@mui/icons-material/People"; +import HistoryIcon from "@mui/icons-material/History"; +import BoltIcon from "@mui/icons-material/Bolt"; +import CableIcon from "@mui/icons-material/Cable"; + +type Section = { + path: string; + icon: React.ReactNode; + labelKey: string; +}; + +const SECTIONS: Section[] = [ + { path: "/admin-settings", icon: , labelKey: "common:settings" }, + { path: "/admin-users", icon: , labelKey: "adminSettings.users.heading" }, + { path: "/admin-logs", icon: , labelKey: "adminSettings.logs.heading" }, + { path: "/admin-triggers", icon: , labelKey: "adminSettings.triggers.heading" }, + { path: "/admin-connections", icon: , labelKey: "adminSettings.connections.heading" }, +]; + +/** The Administration hub -- the sidebar now links here instead of listing every admin sub-page + * itself (see Sidebar's doc comment). Each row just navigates to that sub-page's own route; + * those pages' own back buttons return here (see AppLayout's useRouteChrome), and this page's + * own back button goes to the Dashboard, same as every other top-level page reachable directly + * from the sidebar (Models, Collections, Tags, Downloads). */ +export default function AdminPage() { + const { t } = useTranslation(["app", "common"]); + const navigate = useNavigate(); + + return ( + (theme.palette.mode === "dark" ? "transparent" : "divider"), + }} + > + + {SECTIONS.map((section, idx) => ( + navigate(section.path)} + divider={idx < SECTIONS.length - 1} + sx={{ py: 1.5 }} + > + {section.icon} + + + + ))} + + + ); +} diff --git a/frontend/src/pages/AdminSettingsPage/index.tsx b/frontend/src/pages/AdminSettingsPage/index.tsx index 15787c2..1d4c1e9 100644 --- a/frontend/src/pages/AdminSettingsPage/index.tsx +++ b/frontend/src/pages/AdminSettingsPage/index.tsx @@ -17,9 +17,9 @@ type Props = { export default function AdminSettingsPage({ onUnauthorized, onPreviewModeChanged }: Props) { return ( }> + - ); }