mirror of
https://github.com/TautvydasDerzinskas/Thingport.git
synced 2026-10-21 14:12:25 -05:00
feat: admin menu updates
This commit is contained in:
1 parent
dd2652db08
commit
77cf0ab69f
6 files changed
+114
-135
No files matched your search
@@ -50,24 +50,24 @@ Instead of having your collection scattered across different websites and your f
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/01_dashboard.png" width="100%" alt="Dashboard"><br><sub><b>Dashboard</b></sub></td>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/02_models.png" width="100%" alt="Models"><br><sub><b>Models</b></sub></td>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/03_model_details.png" width="100%" alt="Model details"><br><sub><b>Model Details</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/01_dashboard.png" target="_blank"><img src="frontend/src/assets/screenshots/01_dashboard.png" width="100%" alt="Dashboard"></a><br><sub><b>Dashboard</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/02_models.png" target="_blank"><img src="frontend/src/assets/screenshots/02_models.png" width="100%" alt="Models"></a><br><sub><b>Models</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/03_model_details.png" target="_blank"><img src="frontend/src/assets/screenshots/03_model_details.png" width="100%" alt="Model details"></a><br><sub><b>Model Details</b></sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/04_model_details_3d_preview.png" width="100%" alt="3D preview"><br><sub><b>3D Preview</b></sub></td>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/05_collections.png" width="100%" alt="Collections"><br><sub><b>Collections</b></sub></td>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/06_tags.png" width="100%" alt="Tags"><br><sub><b>Tags</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/04_model_details_3d_preview.png" target="_blank"><img src="frontend/src/assets/screenshots/04_model_details_3d_preview.png" width="100%" alt="3D preview"></a><br><sub><b>3D Preview</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/05_collections.png" target="_blank"><img src="frontend/src/assets/screenshots/05_collections.png" width="100%" alt="Collections"></a><br><sub><b>Collections</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/06_tags.png" target="_blank"><img src="frontend/src/assets/screenshots/06_tags.png" width="100%" alt="Tags"></a><br><sub><b>Tags</b></sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/07_downloads.png" width="100%" alt="Downloads"><br><sub><b>Downloads</b></sub></td>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/08_my_models.png" width="100%" alt="My models"><br><sub><b>My Models</b></sub></td>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/09_dark_theme.png" width="100%" alt="Dark theme"><br><sub><b>Dark Theme</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/07_downloads.png" target="_blank"><img src="frontend/src/assets/screenshots/07_downloads.png" width="100%" alt="Downloads"></a><br><sub><b>Downloads</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/08_my_models.png" target="_blank"><img src="frontend/src/assets/screenshots/08_my_models.png" width="100%" alt="My models"></a><br><sub><b>My Models</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/09_dark_theme.png" target="_blank"><img src="frontend/src/assets/screenshots/09_dark_theme.png" width="100%" alt="Dark theme"></a><br><sub><b>Dark Theme</b></sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/10_extension_printables.png" width="100%" alt="Thingport Grab on Printables"><br><sub><b>Thingport Grab — Printables</b></sub></td>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/11_extension_thingyverse.png" width="100%" alt="Thingport Grab on Thingiverse"><br><sub><b>Thingport Grab — Thingiverse</b></sub></td>
|
||||
<td width="33%"><img src="frontend/src/assets/screenshots/12_extension_makerworld.png" width="100%" alt="Thingport Grab on MakerWorld"><br><sub><b>Thingport Grab — MakerWorld</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/10_extension_printables.png" target="_blank"><img src="frontend/src/assets/screenshots/10_extension_printables.png" width="100%" alt="Thingport Grab on Printables"></a><br><sub><b>Thingport Grab — Printables</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/11_extension_thingyverse.png" target="_blank"><img src="frontend/src/assets/screenshots/11_extension_thingyverse.png" width="100%" alt="Thingport Grab on Thingiverse"></a><br><sub><b>Thingport Grab — Thingiverse</b></sub></td>
|
||||
<td width="33%"><a href="frontend/src/assets/screenshots/12_extension_makerworld.png" target="_blank"><img src="frontend/src/assets/screenshots/12_extension_makerworld.png" width="100%" alt="Thingport Grab on MakerWorld"></a><br><sub><b>Thingport Grab — MakerWorld</b></sub></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -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={<ChangePasswordPage onUnauthorized={onUnauthorized} />}
|
||||
/>
|
||||
<Route path="/downloads" element={<DownloadPage />} />
|
||||
<Route
|
||||
path="/admin"
|
||||
element={isAdmin ? <AdminPage /> : <Navigate to="/" replace />}
|
||||
/>
|
||||
<Route
|
||||
path="/admin-settings"
|
||||
element={
|
||||
|
||||
@@ -96,21 +96,26 @@ function useRouteChrome() {
|
||||
// Overridden by DownloadPage's usePageHeader once translations resolve.
|
||||
title = t("sidebar.downloads");
|
||||
onBack = () => 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 };
|
||||
|
||||
@@ -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<string[]>([]);
|
||||
|
||||
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
|
||||
<>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
{collapsed ? (
|
||||
<>
|
||||
<CollapsedNavIcon
|
||||
icon={<SettingsIcon fontSize="small" />}
|
||||
label={t("common:settings")}
|
||||
selected={onAdminSettings}
|
||||
onClick={() => navigate("/admin-settings")}
|
||||
/>
|
||||
<CollapsedNavIcon
|
||||
icon={<PeopleIcon fontSize="small" />}
|
||||
label={t("adminSettings.users.heading")}
|
||||
selected={onAdminUsers}
|
||||
onClick={() => navigate("/admin-users")}
|
||||
/>
|
||||
<CollapsedNavIcon
|
||||
icon={<HistoryIcon fontSize="small" />}
|
||||
label={t("adminSettings.logs.heading")}
|
||||
selected={onAdminLogs}
|
||||
onClick={() => navigate("/admin-logs")}
|
||||
/>
|
||||
<CollapsedNavIcon
|
||||
icon={<BoltIcon fontSize="small" />}
|
||||
label={t("adminSettings.triggers.heading")}
|
||||
selected={onAdminTriggers}
|
||||
onClick={() => navigate("/admin-triggers")}
|
||||
/>
|
||||
<CollapsedNavIcon
|
||||
icon={<CableIcon fontSize="small" />}
|
||||
label={t("adminSettings.connections.heading")}
|
||||
selected={onAdminConnections}
|
||||
onClick={() => navigate("/admin-connections")}
|
||||
/>
|
||||
</>
|
||||
<CollapsedNavIcon
|
||||
icon={<AdminPanelSettingsIcon fontSize="small" />}
|
||||
label={t("sidebar.administration")}
|
||||
selected={onAdmin}
|
||||
onClick={() => navigate("/admin")}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<ListItemButton
|
||||
selected={false}
|
||||
onClick={() => setAdminExpanded(v => !v)}
|
||||
sx={{ borderRadius: 1, mb: 0.5, ...navRowSx(onAdmin) }}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 30 }}>
|
||||
<AdminPanelSettingsIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("sidebar.administration")} primaryTypographyProps={{ variant: "body2" }} />
|
||||
{adminOpen ? <ExpandLessIcon fontSize="small" /> : <ExpandMoreIcon fontSize="small" />}
|
||||
</ListItemButton>
|
||||
<Collapse in={adminOpen}>
|
||||
<List disablePadding>
|
||||
<ListItemButton
|
||||
selected={onAdminSettings}
|
||||
onClick={() => navigate("/admin-settings")}
|
||||
sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminSettings) }}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 30 }}>
|
||||
<SettingsIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("common:settings")} primaryTypographyProps={{ variant: "body2" }} />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
selected={onAdminUsers}
|
||||
onClick={() => navigate("/admin-users")}
|
||||
sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminUsers) }}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 30 }}>
|
||||
<PeopleIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("adminSettings.users.heading")} primaryTypographyProps={{ variant: "body2" }} />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
selected={onAdminLogs}
|
||||
onClick={() => navigate("/admin-logs")}
|
||||
sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminLogs) }}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 30 }}>
|
||||
<HistoryIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("adminSettings.logs.heading")} primaryTypographyProps={{ variant: "body2" }} />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
selected={onAdminTriggers}
|
||||
onClick={() => navigate("/admin-triggers")}
|
||||
sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminTriggers) }}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 30 }}>
|
||||
<BoltIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("adminSettings.triggers.heading")} primaryTypographyProps={{ variant: "body2" }} />
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
selected={onAdminConnections}
|
||||
onClick={() => navigate("/admin-connections")}
|
||||
sx={{ borderRadius: 1, mb: 0.5, pl: 4, ...navRowSx(onAdminConnections) }}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 30 }}>
|
||||
<CableIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("adminSettings.connections.heading")} primaryTypographyProps={{ variant: "body2" }} />
|
||||
</ListItemButton>
|
||||
</List>
|
||||
</Collapse>
|
||||
</>
|
||||
<ListItemButton
|
||||
selected={onAdmin}
|
||||
onClick={() => navigate("/admin")}
|
||||
sx={{ borderRadius: 1, mb: 0.5, ...navRowSx(onAdmin) }}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 30 }}>
|
||||
<AdminPanelSettingsIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("sidebar.administration")} primaryTypographyProps={{ variant: "body2" }} />
|
||||
</ListItemButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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: <SettingsIcon fontSize="small" />, labelKey: "common:settings" },
|
||||
{ path: "/admin-users", icon: <PeopleIcon fontSize="small" />, labelKey: "adminSettings.users.heading" },
|
||||
{ path: "/admin-logs", icon: <HistoryIcon fontSize="small" />, labelKey: "adminSettings.logs.heading" },
|
||||
{ path: "/admin-triggers", icon: <BoltIcon fontSize="small" />, labelKey: "adminSettings.triggers.heading" },
|
||||
{ path: "/admin-connections", icon: <CableIcon fontSize="small" />, 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 (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{
|
||||
maxWidth: 480,
|
||||
borderRadius: "12px",
|
||||
borderColor: (theme) => (theme.palette.mode === "dark" ? "transparent" : "divider"),
|
||||
}}
|
||||
>
|
||||
<List disablePadding>
|
||||
{SECTIONS.map((section, idx) => (
|
||||
<ListItemButton
|
||||
key={section.path}
|
||||
onClick={() => navigate(section.path)}
|
||||
divider={idx < SECTIONS.length - 1}
|
||||
sx={{ py: 1.5 }}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 36 }}>{section.icon}</ListItemIcon>
|
||||
<ListItemText primary={t(section.labelKey)} primaryTypographyProps={{ variant: "body2", fontWeight: 600 }} />
|
||||
<ChevronRightIcon fontSize="small" sx={{ color: "text.disabled" }} />
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -17,9 +17,9 @@ type Props = {
|
||||
export default function AdminSettingsPage({ onUnauthorized, onPreviewModeChanged }: Props) {
|
||||
return (
|
||||
<Stack spacing={4} divider={<Divider />}>
|
||||
<ThingiverseSection onUnauthorized={onUnauthorized} />
|
||||
<StorageSection onUnauthorized={onUnauthorized} />
|
||||
<PreviewsSection onUnauthorized={onUnauthorized} onSaved={onPreviewModeChanged} />
|
||||
<ThingiverseSection onUnauthorized={onUnauthorized} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user