"use client"; import Image from "next/image"; import Link from "next/link"; import { FreeMode } from "swiper/modules"; import { Swiper, SwiperSlide } from "swiper/react"; import "swiper/css"; import "swiper/css/free-mode"; import { findNodeBySlug } from "@/lib/categoryTree"; import { categoryHref, categorySlugFromLinkUrl } from "@/lib/slug"; import type { CategoryNode, StaticImageSlot } from "@/lib/types"; // Fully slot-driven: each image in the "categories" slot carries its own // linkUrl (e.g. "...?category=mobile"), whose `category` param is the real // DB slug — that's what identifies which category a photo is FOR (used to // build the real, in-site link target). The visible caption is the // business's own titleFa for that image, taken as typed — a category the // business hasn't uploaded a photo for simply isn't shown here, no // placeholder, per the "only what's in the panel" brief. // See AGENTS.md "Static-image slots" for the mismatch this replaced. export default function CategoryCarousel({ roots, slot, }: { roots: CategoryNode[]; slot?: StaticImageSlot; }) { const items = (slot?.images ?? []) .map((img) => { const catSlug = categorySlugFromLinkUrl(img.linkUrl); const cat = catSlug ? findNodeBySlug(roots, catSlug) : undefined; if (!cat) return null; const label = img.titleFa || img.titleEn || cat.nameFa || cat.name; return { key: img.id, art: img.url, label, href: categoryHref(cat.id, cat.nameFa, cat.name) }; }) .filter((x): x is { key: string; art: string; label: string; href: string } => Boolean(x)); if (items.length === 0) return null; return (
{items.map(({ key, art, label, href }) => ( {/* A real border, not ring/box-shadow — box-shadow rings can paint oddly against Swiper's transformed slide track (looked like the border "leaking" into the row above). border-box sizing (this site's global reset) means the 1px border adds no layout shift. */} {label} {label} ))}
); }