feat(web): app shell with sidebar nav, language switch, sign out

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 23:01:42 +02:00
parent 057a00c413
commit 684469273f
3 changed files with 120 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
import { NavLink, Outlet, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useLogout } from "../api/queries";
import { Button } from "@/components/ui/button";
import { LangSwitch } from "./lang-switch";
const FUTURE = ["vocabularies", "authorities", "fields", "search"] as const;
export function AppShell() {
const { t } = useTranslation();
const navigate = useNavigate();
const logout = useLogout();
const onSignOut = () =>
logout.mutate(undefined, {
onSuccess: () => navigate("/login", { replace: true }),
});
return (
<div className="flex min-h-screen">
<aside className="w-44 shrink-0 border-r bg-neutral-50 p-3">
<div className="mb-4 font-semibold">{t("app.name")}</div>
<nav className="space-y-1 text-sm">
<NavLink
to="/objects"
className={({ isActive }) =>
`block rounded px-2 py-1 ${isActive ? "bg-neutral-200 font-medium" : ""}`
}
>
{t("nav.objects")}
</NavLink>
{FUTURE.map((key) => (
<button
key={key}
disabled
title={t("nav.soon")}
className="block w-full cursor-not-allowed rounded px-2 py-1 text-left text-neutral-400"
>
{t(`nav.${key}`)}
</button>
))}
</nav>
</aside>
<div className="flex flex-1 flex-col">
<header className="flex items-center gap-4 border-b px-4 py-2">
<div className="flex-1" />
<LangSwitch />
<Button variant="ghost" size="sm" onClick={onSignOut}>
{t("auth.signOut")}
</Button>
</header>
<main className="flex-1 overflow-hidden">
<Outlet />
</main>
</div>
</div>
);
}