Fix dead "Katalog" public nav link (Task #50)

Problem: The "Katalog" nav link pointed to "/" — the same page the
visitor is usually on — so it appeared to do nothing, while the actual
"Skill-Katalog" grid sits at the bottom beneath the educational section.

Changes:
- catalog.tsx: gave the "Skill-Katalog" <section> a stable anchor
  (id="skill-katalog") plus scroll-mt-24 to clear the sticky header.
- public-layout.tsx: replaced the generic NAV map with explicit nav
  buttons. The "Katalog" link now has an onClick handler that:
  - smooth-scrolls to #skill-katalog when already on "/",
  - navigates to "/" then scrolls when on another public page.
  A scrollToCatalog() helper uses requestAnimationFrame polling (up to
  20 frames) so the scroll waits until the catalog section has mounted
  after navigation. Active-state highlight for "/" is preserved.

No changes to skill cards, search/filter, or educational content.
Typecheck passes; verified the nav renders with "Katalog" active.

Replit-Task-Id: c1ed232e-e513-4cb8-9079-d239f5d5030c
This commit is contained in:
amertensreplit 2026-06-13 09:01:26 +00:00
parent 3c6abf1787
commit cbed6b2062
3 changed files with 38 additions and 18 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -3,13 +3,29 @@ import { Shield, Search, ShieldCheck, Settings, LayoutDashboard } from "lucide-r
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
const NAV = [
{ href: "/", label: "Katalog", match: (l: string) => l === "/" },
{ href: "/pruefen", label: "Skill prüfen", match: (l: string) => l.startsWith("/pruefen") },
];
const CATALOG_ANCHOR_ID = "skill-katalog";
function scrollToCatalog(attempts = 20) {
const el = document.getElementById(CATALOG_ANCHOR_ID);
if (el) {
el.scrollIntoView({ behavior: "smooth", block: "start" });
} else if (attempts > 0) {
requestAnimationFrame(() => scrollToCatalog(attempts - 1));
}
}
export function PublicLayout({ children }: { children: React.ReactNode }) {
const [location] = useLocation();
const [location, setLocation] = useLocation();
const handleCatalogClick = (e: React.MouseEvent) => {
e.preventDefault();
if (location === "/") {
scrollToCatalog();
} else {
setLocation("/");
scrollToCatalog();
}
};
return (
<div className="flex min-h-screen flex-col bg-background text-foreground">
@ -20,18 +36,22 @@ export function PublicLayout({ children }: { children: React.ReactNode }) {
<span className="text-lg font-bold tracking-tight">SkillGuard</span>
</Link>
<nav className="flex items-center gap-2">
{NAV.map((item) => (
<Button
key={item.href}
asChild
variant={item.match(location) ? "secondary" : "ghost"}
size="sm"
>
<Link href={item.href}>
{item.label}
</Link>
</Button>
))}
<Button
asChild
variant={location === "/" ? "secondary" : "ghost"}
size="sm"
>
<Link href="/" onClick={handleCatalogClick}>
Katalog
</Link>
</Button>
<Button
asChild
variant={location.startsWith("/pruefen") ? "secondary" : "ghost"}
size="sm"
>
<Link href="/pruefen">Skill prüfen</Link>
</Button>
<Button asChild variant="outline" size="sm" className="ml-1">
<Link href="/admin">
<Settings className="mr-1.5 h-4 w-4" />

View file

@ -59,7 +59,7 @@ export default function Catalog() {
<PublicEducation />
<section className="space-y-6">
<section id="skill-katalog" className="scroll-mt-24 space-y-6">
<div className="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
<div>
<h2 className="text-2xl font-bold tracking-tight">Skill-Katalog</h2>