diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx
index 8b5de3b..70aa185 100644
--- a/app/(app)/layout.tsx
+++ b/app/(app)/layout.tsx
@@ -1,4 +1,4 @@
-import { Sidebar } from "@/components/layout/Sidebar";
+import { AppShell } from "@/components/layout/AppShell";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { prisma } from "@/lib/prisma";
@@ -24,12 +24,5 @@ export default async function AppLayout({ children }: { children: React.ReactNod
redirect("/login?next=/machines");
}
- return (
-
- );
+ return {children};
}
diff --git a/components/layout/AppShell.tsx b/components/layout/AppShell.tsx
new file mode 100644
index 0000000..173d5de
--- /dev/null
+++ b/components/layout/AppShell.tsx
@@ -0,0 +1,72 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { Menu } from "lucide-react";
+import { usePathname } from "next/navigation";
+import { Sidebar } from "@/components/layout/Sidebar";
+import { UtilityControls } from "@/components/layout/UtilityControls";
+import { useI18n } from "@/lib/i18n/useI18n";
+
+export function AppShell({ children }: { children: React.ReactNode }) {
+ const { t } = useI18n();
+ const pathname = usePathname();
+ const [drawerOpen, setDrawerOpen] = useState(false);
+
+ useEffect(() => {
+ setDrawerOpen(false);
+ }, [pathname]);
+
+ useEffect(() => {
+ if (!drawerOpen) return;
+ const onKey = (event: KeyboardEvent) => {
+ if (event.key === "Escape") setDrawerOpen(false);
+ };
+ window.addEventListener("keydown", onKey);
+ document.body.style.overflow = "hidden";
+ return () => {
+ window.removeEventListener("keydown", onKey);
+ document.body.style.overflow = "";
+ };
+ }, [drawerOpen]);
+
+ return (
+
+
+
+ {drawerOpen && (
+
+
+ )}
+
+ );
+}
diff --git a/components/layout/Sidebar.tsx b/components/layout/Sidebar.tsx
index 030c0b2..29834c5 100644
--- a/components/layout/Sidebar.tsx
+++ b/components/layout/Sidebar.tsx
@@ -3,49 +3,9 @@
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
-import { BarChart3, LayoutGrid, LogOut, Settings, Wrench } from "lucide-react";
+import { BarChart3, LayoutGrid, LogOut, Settings, Wrench, X } from "lucide-react";
import { useI18n } from "@/lib/i18n/useI18n";
-const THEME_COOKIE = "mis_theme";
-
-const SunIcon = ({ className }: { className?: string }) => (
-
-);
-
-const MoonIcon = ({ className }: { className?: string }) => (
-
-);
-
const items = [
{ href: "/overview", labelKey: "nav.overview", icon: LayoutGrid },
{ href: "/machines", labelKey: "nav.machines", icon: Wrench },
@@ -53,11 +13,16 @@ const items = [
{ href: "/settings", labelKey: "nav.settings", icon: Settings },
] as const;
-export function Sidebar() {
+type SidebarProps = {
+ variant?: "desktop" | "drawer";
+ onNavigate?: () => void;
+ onClose?: () => void;
+};
+
+export function Sidebar({ variant = "desktop", onNavigate, onClose }: SidebarProps) {
const pathname = usePathname();
const router = useRouter();
- const { locale, setLocale, t } = useI18n();
- const [theme, setTheme] = useState<"dark" | "light">("dark");
+ const { t } = useI18n();
const [me, setMe] = useState<{
user?: { name?: string | null; email?: string | null };
org?: { name?: string | null };
@@ -83,36 +48,36 @@ export function Sidebar() {
};
}, []);
- useEffect(() => {
- const current = document.documentElement.getAttribute("data-theme");
- if (current === "light" || current === "dark") {
- setTheme(current);
- }
- }, []);
-
- function applyTheme(next: "light" | "dark") {
- document.documentElement.setAttribute("data-theme", next);
- document.cookie = `${THEME_COOKIE}=${next}; Path=/; Max-Age=31536000; SameSite=Lax`;
- setTheme(next);
- }
-
- function toggleTheme() {
- applyTheme(theme === "light" ? "dark" : "light");
- }
-
async function onLogout() {
await fetch("/api/logout", { method: "POST" });
router.push("/login");
router.refresh();
+ onNavigate?.();
}
const roleKey = (me?.membership?.role || "MEMBER").toLowerCase();
+ const shellClass = [
+ "relative z-20 flex flex-col border-r border-white/10 bg-black/40",
+ variant === "desktop" ? "hidden md:flex h-screen w-64" : "flex h-full w-72 max-w-[85vw]",
+ ].join(" ");
return (
-