import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { Navbar } from "@/components/layout/navbar"; import { Footer } from "@/components/layout/footer"; import { CafeJsonLd } from "@/components/seo/cafe-json-ld"; import { getCafe, getCafeMenu, getCafeReviews, getAllCafeSlugs, discoverCafes } from "@/lib/api"; import { MapPin, Phone, Clock, Star, BadgeCheck, Instagram, Globe, ChevronLeft, Wifi, Coffee, Users } from "lucide-react"; import { getDayLabel, PRICE_TIER_LABELS, NOISE_LABELS, formatRating, cn } from "@/lib/utils"; import { CafeCard } from "@/components/cafe/cafe-card"; const BASE = process.env.NEXT_PUBLIC_SITE_URL ?? "https://find.meezi.ir"; export async function generateStaticParams() { const slugs = await getAllCafeSlugs(); const locales = ["fa", "en"]; return locales.flatMap((locale) => slugs.map((slug) => ({ locale, slug }))); } export async function generateMetadata({ params, }: { params: Promise<{ locale: string; slug: string }>; }): Promise { const { locale, slug } = await params; const cafe = await getCafe(slug); if (!cafe) return { title: "Cafe not found" }; const name = locale === "en" && cafe.nameEn ? cafe.nameEn : cafe.name; const description = cafe.description ?? (locale === "fa" ? `${name} در ${cafe.city ?? "ایران"} — امتیاز ${formatRating(cafe.averageRating)} از ${cafe.reviewCount} نظر` : `${name} in ${cafe.city ?? "Iran"} — rated ${formatRating(cafe.averageRating)} from ${cafe.reviewCount} reviews`); const ogImage = cafe.coverImageUrl ?? `${BASE}/api/og?t=${encodeURIComponent(name)}&s=${encodeURIComponent(description)}`; return { title: name, description, openGraph: { type: "website", title: name, description, images: [{ url: ogImage, width: 1200, height: 630, alt: name }], locale: locale === "fa" ? "fa_IR" : "en_US", }, twitter: { card: "summary_large_image", title: name, description, images: [ogImage] }, alternates: { canonical: `${BASE}/${locale}/cafe/${slug}`, languages: { fa: `${BASE}/fa/cafe/${slug}`, en: `${BASE}/en/cafe/${slug}`, }, }, }; } const DAY_ORDER = ["sat", "sun", "mon", "tue", "wed", "thu", "fri"] as const; export default async function CafePage({ params, }: { params: Promise<{ locale: string; slug: string }>; }) { const { locale, slug } = await params; const t = await getTranslations({ locale, namespace: "cafe" }); const isFa = locale === "fa"; // Resolve the café first so an unknown slug 404s cleanly instead of doing // (and potentially erroring on) the menu/review fetches. const cafe = await getCafe(slug); if (!cafe) notFound(); const [menu, reviews] = await Promise.all([ getCafeMenu(slug), getCafeReviews(slug), ]); const name = isFa ? cafe.name : (cafe.nameEn ?? cafe.name); // discoverProfile may be absent for cafés that never filled it in — fall back // to an empty profile so the page renders instead of throwing a 500. const profile = cafe.discoverProfile ?? { themes: [], size: null, floors: null, vibes: [], occasions: [], spaceFeatures: [], noiseLevel: null, priceTier: null, }; const priceTier = profile.priceTier; // Similar cafes const similar = cafe.city ? (await discoverCafes({ city: cafe.city, sort: "rating" })) .filter((c) => c.slug !== slug) .slice(0, 4) : []; return ( <>
{/* Hero cover */}
{cafe.coverImageUrl ? ( {name} ) : (
)} {/* Gradient overlay */}
{/* Back link */} {isFa ? : } {t("backToSearch")} {/* Open badge */}
{cafe.isOpenNow ? t("openNow") : t("closedNow")}
{/* Name / city overlay */}
{cafe.logoUrl && ( )}

{name} {cafe.isVerified && ( )}

{cafe.city && (

{cafe.city} {cafe.address && ` — ${cafe.address}`}

)}
{/* Quick stats row */}
{cafe.reviewCount > 0 && (
{formatRating(cafe.averageRating)} ({cafe.reviewCount} {t("reviews")})
)} {priceTier && ( {PRICE_TIER_LABELS[priceTier]?.[isFa ? "fa" : "en"] ?? priceTier} )} {profile.noiseLevel && ( {NOISE_LABELS[profile.noiseLevel]?.[isFa ? "fa" : "en"] ?? profile.noiseLevel} )}
{/* Main column */}
{/* Description */} {cafe.description && (

{t("about")}

{cafe.description}

)} {/* Attributes */} {(profile.themes.length > 0 || profile.vibes.length > 0 || profile.occasions.length > 0 || profile.spaceFeatures.length > 0) && (

{t("features")}

{profile.themes.length > 0 && ( )} {profile.vibes.length > 0 && ( )} {profile.occasions.length > 0 && ( )} {profile.spaceFeatures.length > 0 && ( )}
)} {/* Gallery */} {cafe.galleryUrls.length > 0 && (

{t("gallery")}

{cafe.galleryUrls.map((url, i) => ( {`${name} ))}
)} {/* Menu preview */} {menu && menu.categories.length > 0 && (

{t("menu")}

{menu.categories.slice(0, 3).map((cat) => (

{isFa ? cat.name : (cat.nameEn ?? cat.name)}

{cat.items.slice(0, 4).map((item) => (
{item.imageUrl && ( )}

{isFa ? item.name : (item.nameEn ?? item.name)}

{item.description && (

{item.description}

)}
{new Intl.NumberFormat(isFa ? "fa-IR" : "en-US").format(item.price)}
))}
))}
{t("viewMenu")}
)} {/* Reviews */}

{t("reviewsTab")}

{reviews.length === 0 ? (

{t("noReviews")}

) : (
{reviews.map((r) => (

{r.authorName}

{Array.from({ length: 5 }).map((_, i) => ( ))}
{r.comment && (

{r.comment}

)}
))}
)}
{/* Sidebar */}
{/* Similar cafes */} {similar.length > 0 && (

{t("similarCafes")}

{similar.map((c) => ( ))}
)}