From 7790361a0a6e4c933f13878d2f45ff0bf8f28c22 Mon Sep 17 00:00:00 2001 From: mdares Date: Tue, 6 Jan 2026 21:51:08 +0000 Subject: [PATCH] ui fixes --- app/(app)/layout.tsx | 11 +-- components/layout/AppShell.tsx | 72 ++++++++++++++ components/layout/Sidebar.tsx | 135 ++++++-------------------- components/layout/UtilityControls.tsx | 115 ++++++++++++++++++++++ lib/i18n/localeStore.ts | 54 +++++++++++ lib/i18n/useI18n.ts | 15 +-- 6 files changed, 283 insertions(+), 119 deletions(-) create mode 100644 components/layout/AppShell.tsx create mode 100644 components/layout/UtilityControls.tsx create mode 100644 lib/i18n/localeStore.ts 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 ( -
-
- -
{children}
-
-
- ); + 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 ( +
+
+ +
+
+
+ +
+ {t("sidebar.productTitle")} +
+
+ +
+
{children}
+
+
+ + {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 ( -