Files
meezi/web/finder/src/components/layout/navbar.tsx
T
soroush.asadi 42d4cb896a feat(finder): AI-powered cafe finder PWA with Next.js 16
Public cafe discovery app:
- SEO-optimised pages: home, /cafe/[slug], /search, /city/[city]
- AI search bar with natural language queries
- Structured data (JSON-LD) for Google rich results
- City browsing, rating/filter sidebar, similar cafes
- Review listing, full menu preview, working-hours card
- Web App Manifest + offline fallback page (PWA)
- Next.js 16: params/searchParams typed as Promise<{}>
- Fix Lucide icon title→aria-label (type removed upstream)
- "use client" on offline page (onClick handler)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-27 21:34:47 +03:30

106 lines
3.7 KiB
TypeScript

"use client";
import { useState } from "react";
import { useTranslations, useLocale } from "next-intl";
import { useRouter, usePathname } from "next/navigation";
import { Search, Menu, X, Globe, MapPin } from "lucide-react";
import { cn } from "@/lib/utils";
export function Navbar() {
const t = useTranslations("nav");
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const [open, setOpen] = useState(false);
const otherLocale = locale === "fa" ? "en" : "fa";
const otherPath = pathname.replace(`/${locale}`, `/${otherLocale}`);
const links = [
{ href: `/${locale}`, label: t("home") },
{ href: `/${locale}/search`, label: t("search") },
];
return (
<header className="sticky top-0 z-40 w-full border-b border-gray-100 bg-white/95 backdrop-blur-sm">
<div className="mx-auto flex h-14 max-w-7xl items-center gap-3 px-4 sm:px-6">
{/* Logo */}
<a href={`/${locale}`} className="flex items-center gap-2 shrink-0">
<div className="flex h-8 w-8 items-center justify-center rounded-xl bg-brand-700">
<MapPin className="h-4 w-4 text-white" />
</div>
<span className="text-base font-bold text-gray-900">
{locale === "fa" ? "میزی‌یاب" : "Meezi Finder"}
</span>
</a>
{/* Desktop nav */}
<nav className="hidden flex-1 items-center gap-1 sm:flex ms-4">
{links.map((l) => (
<a
key={l.href}
href={l.href}
className={cn(
"rounded-lg px-3 py-1.5 text-sm font-medium transition-colors",
pathname === l.href
? "bg-brand-50 text-brand-700"
: "text-gray-600 hover:bg-gray-100"
)}
>
{l.label}
</a>
))}
</nav>
<div className="flex flex-1 items-center justify-end gap-2 sm:flex-none">
{/* Search shortcut */}
<a
href={`/${locale}/search`}
className="flex items-center gap-2 rounded-xl border border-gray-200 bg-gray-50 px-3 py-1.5 text-sm text-gray-400 transition hover:border-brand-300 hover:bg-brand-50 hover:text-brand-700 sm:min-w-[180px]"
>
<Search className="h-3.5 w-3.5 shrink-0" />
<span className="hidden sm:inline">
{locale === "fa" ? "جستجوی کافه..." : "Search cafes..."}
</span>
</a>
{/* Language toggle */}
<a
href={otherPath}
className="flex items-center gap-1 rounded-lg px-2 py-1.5 text-xs font-medium text-gray-500 transition hover:bg-gray-100"
title={otherLocale === "fa" ? "فارسی" : "English"}
>
<Globe className="h-3.5 w-3.5" />
<span>{otherLocale === "fa" ? "FA" : "EN"}</span>
</a>
{/* Mobile menu toggle */}
<button
className="rounded-lg p-1.5 text-gray-500 hover:bg-gray-100 sm:hidden"
onClick={() => setOpen(!open)}
aria-label={open ? t("closeMenu") : t("openMenu")}
>
{open ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</button>
</div>
</div>
{/* Mobile menu */}
{open && (
<div className="border-t border-gray-100 bg-white px-4 py-3 sm:hidden">
{links.map((l) => (
<a
key={l.href}
href={l.href}
onClick={() => setOpen(false)}
className="block rounded-lg px-3 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
{l.label}
</a>
))}
</div>
)}
</header>
);
}