open-prompt / src /components /layout /header.tsx
GitHub Action
Automated sync to Hugging Face
bcce530
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { Suspense } from "react"
import { Sparkles, Menu, X, Plus, Loader2 } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ThemeToggle } from "@/components/ui/theme-toggle"
import { UserButton } from "@/components/auth/user-button"
import { NotificationBell } from "@/components/notifications/notification-center"
import { OllamaSettingsModal } from "@/components/ollama/ollama-settings"
import { useState } from "react"
import { cn } from "@/lib/utils"
const navLinks = [
{ href: "/explore", label: "Explore" },
{ href: "/tools", label: "Tools" },
{ href: "/thunderdome", label: "Thunderdome" },
{ href: "/workflows", label: "Workflows" },
{ href: "/frameworks", label: "Frameworks" },
{ href: "/performance", label: "Performance" },
{ href: "/dashboard", label: "Dashboard" },
]
// Loading fallback for UserButton
function UserButtonFallback() {
return (
<div className="h-9 w-24 rounded-lg bg-muted animate-pulse flex items-center justify-center">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
)
}
export function Header() {
const pathname = usePathname()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return (
<header className="sticky top-0 z-50 w-full border-b border-border glass-strong">
<div className="container mx-auto px-4">
<nav className="flex h-16 items-center justify-between">
{/* Logo */}
<Link href="/" className="flex items-center gap-2 group">
<div className="relative h-9 w-9 overflow-hidden rounded-lg shadow-lg group-hover:shadow-primary/25 transition-shadow">
<img
src="/logos/logo.svg"
alt="OpenPrompt Logo"
className="h-full w-full object-cover"
/>
</div>
<span className="font-semibold text-lg tracking-tight hidden sm:block">
Open<span className="text-primary">Prompt</span>
</span>
</Link>
{/* Desktop Navigation */}
<div className="hidden md:flex items-center gap-1">
{navLinks.map((link) => {
const isActive = pathname === link.href || pathname?.startsWith(`${link.href}/`)
return (
<Link
key={link.href}
href={link.href}
className={cn(
"px-4 py-2 text-sm font-medium transition-colors rounded-lg",
isActive
? "text-foreground bg-muted font-semibold"
: "text-muted-foreground hover:text-foreground hover:bg-muted"
)}
>
{link.label}
</Link>
)
})}
</div>
{/* Right Side Actions */}
<div className="flex items-center gap-2">
<OllamaSettingsModal />
<NotificationBell />
<ThemeToggle />
<Link href="/create" className="hidden sm:flex">
<Button size="sm" className="gap-2">
<Plus className="h-4 w-4" />
Create
</Button>
</Link>
{/* User Button with Suspense boundary */}
<div className="hidden md:flex">
<Suspense fallback={<UserButtonFallback />}>
<UserButton />
</Suspense>
</div>
{/* Mobile Menu Button */}
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden h-9 w-9 rounded-lg border border-border bg-background flex items-center justify-center hover:bg-muted transition-colors"
aria-label="Toggle menu"
>
{mobileMenuOpen ? (
<X className="h-4 w-4" />
) : (
<Menu className="h-4 w-4" />
)}
</button>
</div>
</nav>
{/* Mobile Menu */}
<div
className={cn(
"md:hidden overflow-hidden transition-all duration-300",
mobileMenuOpen ? "max-h-96 pb-4" : "max-h-0"
)}
>
<div className="flex flex-col gap-2 pt-2">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors rounded-lg hover:bg-muted"
onClick={() => setMobileMenuOpen(false)}
>
{link.label}
</Link>
))}
<div className="flex gap-2 pt-2 border-t border-border mt-2">
<Link href="/create" className="flex-1">
<Button size="sm" className="w-full gap-2">
<Plus className="h-4 w-4" />
Create
</Button>
</Link>
</div>
{/* Mobile User Button with Suspense */}
<div className="pt-2">
<Suspense fallback={<UserButtonFallback />}>
<UserButton />
</Suspense>
</div>
</div>
</div>
</div>
</header>
)
}