| "use client"; |
|
|
| import { useEffect, useRef, useState } from "react"; |
| import { Menu, Sun, Moon } from "lucide-react"; |
| import Link from "next/link"; |
| import Image from "next/image"; |
| import { useRouter } from "next/navigation"; |
| import { useTheme } from "./ThemeProvider"; |
| import { Button } from "@/components/ui/button"; |
|
|
| export default function Navbar() { |
| const [isOpen, setIsOpen] = useState(false); |
| const { theme, toggleTheme } = useTheme(); |
| const router = useRouter(); |
|
|
| const adminHoldTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| const adminGestureTriggeredRef = useRef(false); |
|
|
| const clearAdminHoldTimer = () => { |
| if (adminHoldTimerRef.current) { |
| clearTimeout(adminHoldTimerRef.current); |
| adminHoldTimerRef.current = null; |
| } |
| }; |
|
|
| useEffect(() => { |
| const onWindowBlur = () => { |
| clearAdminHoldTimer(); |
| adminGestureTriggeredRef.current = false; |
| }; |
| window.addEventListener("blur", onWindowBlur); |
| return () => { |
| window.removeEventListener("blur", onWindowBlur); |
| }; |
| |
| }, []); |
|
|
| return ( |
| <nav className="fixed inset-x-0 top-0 z-50 bg-background/90 backdrop-blur supports-[backdrop-filter]:bg-background/70 shadow-[0_1px_0_rgba(255,255,255,0.03)]"> |
| <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4 sm:px-6"> |
| <Link |
| href="/" |
| className="flex items-center gap-2.5" |
| onPointerDown={(e) => { |
| if (!e.ctrlKey) return; |
| adminGestureTriggeredRef.current = false; |
| clearAdminHoldTimer(); |
| adminHoldTimerRef.current = setTimeout(() => { |
| adminGestureTriggeredRef.current = true; |
| router.push("/admin"); |
| }, 3000); |
| }} |
| onPointerUp={() => { |
| clearAdminHoldTimer(); |
| }} |
| onPointerLeave={() => { |
| clearAdminHoldTimer(); |
| }} |
| onPointerCancel={() => { |
| clearAdminHoldTimer(); |
| }} |
| onClick={(e) => { |
| if (adminGestureTriggeredRef.current) { |
| e.preventDefault(); |
| adminGestureTriggeredRef.current = false; |
| } |
| }} |
| > |
| <Image |
| src="/teich.svg" |
| alt="TeichAI Logo" |
| width={32} |
| height={32} |
| className="rounded-lg" |
| /> |
| <span className="text-lg font-semibold tracking-tight text-foreground">TeichAI Requests</span> |
| </Link> |
| |
| <div className="hidden items-center gap-1 md:flex"> |
| <Button |
| onClick={toggleTheme} |
| variant="ghost" |
| size="icon" |
| className="ml-1" |
| aria-label="Toggle theme" |
| > |
| {theme === "dark" ? <Sun className="size-4" /> : <Moon className="size-4" />} |
| </Button> |
| |
| <Button asChild className="ml-1"> |
| <a href="https://huggingface.co/TeichAI" target="_blank" rel="noopener noreferrer"> |
| HF Hub |
| </a> |
| </Button> |
| </div> |
| |
| <div className="flex items-center gap-1 md:hidden"> |
| <Button |
| onClick={toggleTheme} |
| variant="ghost" |
| size="icon" |
| aria-label="Toggle theme" |
| > |
| {theme === "dark" ? <Sun className="size-5" /> : <Moon className="size-5" />} |
| </Button> |
| |
| <Button |
| variant="ghost" |
| size="icon" |
| aria-label="Open menu" |
| onClick={() => setIsOpen(!isOpen)} |
| > |
| <Menu className="size-5" /> |
| </Button> |
| </div> |
| </div> |
| |
| {isOpen && ( |
| <div className="border-t border-border bg-background p-4 md:hidden"> |
| <Button asChild className="w-full"> |
| <a href="https://huggingface.co/TeichAI" target="_blank" rel="noopener noreferrer"> |
| HF Hub |
| </a> |
| </Button> |
| </div> |
| )} |
| </nav> |
| ); |
| } |
|
|