Files
gitlab-instance-0a899031_cn…/app/components/Navbar.tsx
2026-03-08 23:20:36 -05:00

173 lines
7.1 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { Link, useLocale, usePathname, useRouter, useTranslation } from "@/i18n/navigation";
import { getStoredUserEmail, pbLogout } from "@/app/lib/pocketbase";
import AuthModal from "./AuthModal";
import ThemeToggle from "./ThemeToggle";
const navLinks = [
{ labelKey: "cities" as const, icon: "🏙️", href: "/" },
{ labelKey: "travel" as const, icon: "✈️", href: "/meetups" },
{ labelKey: "community" as const, icon: "💬", href: "/dating" },
{ labelKey: "explore" as const, icon: "🎒", href: "/#community" },
];
export default function Navbar() {
const { t } = useTranslation("common");
const { t: tNav } = useTranslation("nav");
const locale = useLocale();
const pathname = usePathname();
const router = useRouter();
const [mobileOpen, setMobileOpen] = useState(false);
const [authOpen, setAuthOpen] = useState(false);
const [userEmail, setUserEmail] = useState<string | null>(null);
useEffect(() => {
setUserEmail(getStoredUserEmail());
}, []);
const handleLogout = () => {
pbLogout();
setUserEmail(null);
};
function isActive(href: string) {
if (href.startsWith("/#")) return false;
if (href === "/") return pathname === "/";
return pathname.startsWith(href);
}
return (
<nav className="sticky top-0 z-50 bg-white/80 dark:bg-gray-900/80 backdrop-blur-lg border-b border-gray-100 dark:border-gray-800">
<div className="max-w-[1600px] mx-auto px-4 sm:px-6 h-16 flex items-center justify-between">
<Link href="/" className="flex items-center gap-2 shrink-0">
<span className="text-2xl">🌏</span>
<span className="text-lg font-bold text-gray-900 dark:text-gray-100 tracking-tight">
{t("siteName")}
</span>
</Link>
<div className="hidden md:flex items-center gap-1">
{navLinks.map((link) => (
<Link
key={link.labelKey}
href={link.href}
className={`flex items-center gap-1.5 px-4 py-2 rounded-full text-sm font-medium transition-colors ${
isActive(link.href)
? "text-gray-900 dark:text-gray-100 bg-gray-100 dark:bg-gray-800"
: "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800"
}`}
>
<span>{link.icon}</span>
<span>{tNav(link.labelKey)}</span>
</Link>
))}
</div>
<div className="flex items-center gap-2 sm:gap-3">
{userEmail ? (
<div className="flex items-center gap-2">
<span className="hidden sm:block max-w-[120px] truncate text-sm text-gray-600 dark:text-gray-400">
{userEmail}
</span>
<button
onClick={handleLogout}
className="rounded-full border border-gray-200 dark:border-gray-700 px-4 py-2 text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
{t("logout")}
</button>
</div>
) : (
<button
onClick={() => setAuthOpen(true)}
className="hidden sm:block text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-2 rounded-full hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
{t("loginRegister")}
</button>
)}
<button
type="button"
onClick={() => router.replace(pathname, { locale: locale === "zh" ? "en" : "zh" })}
className="inline-flex shrink-0 items-center justify-center rounded-lg p-2 text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
aria-label={locale === "zh" ? "Switch to English" : "切换到中文"}
title={locale === "zh" ? "EN" : "中文"}
>
{locale === "zh" ? "EN" : "中文"}
</button>
<ThemeToggle />
<Link
href="/join"
className="text-sm bg-[#ff4d4f] text-white px-4 py-2 rounded-full hover:bg-[#ff7a45] transition-colors font-medium"
>
{t("explore")}
</Link>
<button
className="md:hidden ml-1 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
onClick={() => setMobileOpen(!mobileOpen)}
>
{mobileOpen ? (
<svg width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path d="M6 18L18 6M6 6l12 12" />
</svg>
) : (
<svg width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path d="M4 6h16M4 12h16M4 18h16" />
</svg>
)}
</button>
</div>
</div>
{mobileOpen && (
<div className="md:hidden border-t border-gray-100 dark:border-gray-800 bg-white dark:bg-gray-900 px-4 py-3 space-y-1">
{navLinks.map((link) => (
<Link
key={link.labelKey}
href={link.href}
className={`flex items-center gap-2 px-4 py-3 rounded-xl text-sm font-medium ${
isActive(link.href)
? "text-gray-900 dark:text-gray-100 bg-gray-50 dark:bg-gray-800"
: "text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800"
}`}
onClick={() => setMobileOpen(false)}
>
<span className="text-lg">{link.icon}</span>
<span>{tNav(link.labelKey)}</span>
</Link>
))}
<button
type="button"
onClick={() => router.replace(pathname, { locale: locale === "zh" ? "en" : "zh" })}
className="block w-full text-left px-4 py-3 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
>
{locale === "zh" ? "EN" : "中文"}
</button>
{userEmail ? (
<div className="pt-2 border-t border-gray-100 dark:border-gray-800 flex items-center justify-between px-4 py-2.5 rounded-lg bg-gray-50 dark:bg-gray-800">
<span className="truncate text-sm text-gray-600 dark:text-gray-400">{userEmail}</span>
<button onClick={() => { handleLogout(); setMobileOpen(false); }} className="text-sm font-medium text-[#ff4d4f]">
{t("logout")}
</button>
</div>
) : (
<button
onClick={() => { setMobileOpen(false); setAuthOpen(true); }}
className="w-full text-left px-4 py-3 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
>
{t("loginRegister")}
</button>
)}
</div>
)}
<AuthModal
isOpen={authOpen}
onClose={() => setAuthOpen(false)}
onSuccess={(email) => setUserEmail(email)}
/>
</nav>
);
}