fix: compact filter popover; feat: i18n (de/en)
Build & Push Docker Image / build (push) Successful in 2m31s

This commit is contained in:
opencode
2026-08-02 13:42:53 +02:00
parent 95bf49509c
commit 2f38889726
19 changed files with 662 additions and 160 deletions
+24 -17
View File
@@ -1,28 +1,31 @@
import { Link, useLocation } from "wouter";
import { LayoutDashboard, Wrench, PlusCircle, BarChart3, LogIn, LogOut, User, ShieldCheck, AlertTriangle, Trash2, Bookmark } from "lucide-react";
import { useTranslation } from "react-i18next";
import { useAuth } from "@/hooks/use-auth";
import { useGetVersion, getGetVersionQueryKey } from "@workspace/api-client-react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { ThemeToggle } from "@/components/theme-toggle";
import { CommandPalette } from "@/components/command-palette";
import { LanguageSwitcher } from "@/components/language-switcher";
export function Layout({ children }: { children: React.ReactNode }) {
const [location] = useLocation();
const { t } = useTranslation();
const { user, isLoading, isAuthenticated, isAdmin, isLocalMode, tier, hasFeature, login, logout } = useAuth();
const { data: version } = useGetVersion({
query: { queryKey: getGetVersionQueryKey(), staleTime: Infinity, retry: false },
});
const links = [
{ href: "/", label: "Dashboard", icon: LayoutDashboard },
{ href: "/tools", label: "Browse Tools", icon: Wrench },
{ href: "/tools/new", label: "Add Tool", icon: PlusCircle },
{ href: "/analytics", label: "Analytics", icon: BarChart3 },
...(hasFeature("watchlist") ? [{ href: "/watchlist", label: "Watchlist", icon: Bookmark }] : []),
...(hasFeature("trash") ? [{ href: "/trash", label: "Trash", icon: Trash2 }] : []),
...(isAdmin ? [{ href: "/admin", label: "Admin", icon: ShieldCheck }] : []),
...(isAdmin ? [{ href: "/admin/redundancy", label: "Redundancy", icon: AlertTriangle }] : []),
{ href: "/", label: t("nav.home"), icon: LayoutDashboard },
{ href: "/tools", label: t("nav.browseTools"), icon: Wrench },
{ href: "/tools/new", label: t("nav.addTool"), icon: PlusCircle },
{ href: "/analytics", label: t("nav.analytics"), icon: BarChart3 },
...(hasFeature("watchlist") ? [{ href: "/watchlist", label: t("nav.watchlist"), icon: Bookmark }] : []),
...(hasFeature("trash") ? [{ href: "/trash", label: t("nav.trash"), icon: Trash2 }] : []),
...(isAdmin ? [{ href: "/admin", label: t("nav.admin"), icon: ShieldCheck }] : []),
...(isAdmin ? [{ href: "/admin/redundancy", label: t("nav.redundancy"), icon: AlertTriangle }] : []),
];
const activeLink = links.find(
@@ -30,11 +33,11 @@ export function Layout({ children }: { children: React.ReactNode }) {
);
const mobileLinks = [
{ href: "/", label: "Home", icon: LayoutDashboard },
{ href: "/tools", label: "Tools", icon: Wrench },
...(hasFeature("watchlist") ? [{ href: "/watchlist", label: "Watchlist", icon: Bookmark }] : []),
...(hasFeature("trash") ? [{ href: "/trash", label: "Trash", icon: Trash2 }] : []),
{ href: "/analytics", label: "Analytics", icon: BarChart3 },
{ href: "/", label: t("nav.home"), icon: LayoutDashboard },
{ href: "/tools", label: t("nav.browseTools"), icon: Wrench },
...(hasFeature("watchlist") ? [{ href: "/watchlist", label: t("nav.watchlist"), icon: Bookmark }] : []),
...(hasFeature("trash") ? [{ href: "/trash", label: t("nav.trash"), icon: Trash2 }] : []),
{ href: "/analytics", label: t("nav.analytics"), icon: BarChart3 },
].slice(0, 5);
return (
@@ -94,7 +97,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
data-testid="button-logout"
>
<LogOut className="w-4 h-4" />
Sign out
{t("auth.signOut")}
</Button>
</div>
) : (
@@ -106,7 +109,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
data-testid="button-login"
>
<LogIn className="w-4 h-4" />
{isLocalMode ? "Sign in" : "Sign in with Keycloak"}
{isLocalMode ? t("auth.signIn") : t("auth.signInKeycloak")}
</Button>
)}
</div>
@@ -135,7 +138,10 @@ export function Layout({ children }: { children: React.ReactNode }) {
<h1 className="text-sm font-semibold text-muted-foreground">
{activeLink?.label ?? "toolr"}
</h1>
<ThemeToggle />
<div className="flex items-center gap-1">
<LanguageSwitcher />
<ThemeToggle />
</div>
</header>
<header className="md:hidden border-b p-4 flex items-center justify-between bg-card">
<Link href="/" className="flex items-center gap-2 text-primary font-bold text-lg">
@@ -143,6 +149,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
<span>toolr</span>
</Link>
<div className="flex items-center gap-1">
<LanguageSwitcher />
<ThemeToggle />
{!isLoading && (
isAuthenticated ? (
@@ -152,7 +159,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
) : (
<Button variant="outline" size="sm" onClick={() => login(location)} data-testid="button-login-mobile">
<LogIn className="w-4 h-4 mr-1" />
Sign in
{t("auth.signIn")}
</Button>
)
)}