Add Meshkee product catalog navigation and details

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-26 19:42:35 +03:30
co-authored by Cursor
parent 00126e5777
commit 34ce4f6925
13 changed files with 1499 additions and 97 deletions
+126
View File
@@ -0,0 +1,126 @@
"use client";
import { ChevronDown, ChevronLeft } from "lucide-react";
import Link from "next/link";
import { useEffect, useMemo, useState } from "react";
import {
categoryPath,
fetchProductCategories,
getLevelTwoProductCategories,
type ProductCategory,
} from "../lib/meshkee";
type ActivePage = "home" | "products" | "about" | "contact";
const navigationItems: {
label: string;
href: string;
key?: ActivePage;
}[] = [
{ label: "صفحه اصلی", href: "/", key: "home" },
{ label: "خدمات", href: "/#services" },
{ label: "اخبار", href: "/#news" },
{ label: "درباره ما", href: "/about-us", key: "about" },
{ label: "تماس با ما", href: "/contact-us", key: "contact" },
];
export default function HeaderNavigation({
active,
mobileOpen,
onNavigate,
}: {
active?: ActivePage;
mobileOpen: boolean;
onNavigate?: () => void;
}) {
const [categories, setCategories] = useState<ProductCategory[]>([]);
useEffect(() => {
const controller = new AbortController();
fetchProductCategories(controller.signal)
.then(setCategories)
.catch(() => {
// Keep the all-products link when categories cannot be loaded.
});
return () => controller.abort();
}, []);
const levelTwoCategories = useMemo(
() => getLevelTwoProductCategories(categories),
[categories],
);
const renderCategoryLinks = () => (
<>
<Link href="/products" onClick={onNavigate}>
همه ماشین‌آلات
<small>ALL PRODUCTS</small>
</Link>
{levelTwoCategories.map((category) => (
<Link
href={categoryPath(category)}
key={category.id}
onClick={onNavigate}
>
{category.nameFa || category.name}
<small dir="ltr">{category.name}</small>
</Link>
))}
</>
);
return (
<>
<nav className="desktop-nav" aria-label="ناوبری اصلی">
<Link className={active === "home" ? "active" : ""} href="/">
صفحه اصلی
</Link>
<div className="product-menu">
<Link
className={active === "products" ? "active" : ""}
href="/products"
>
محصولات
<ChevronDown size={14} />
</Link>
<div className="product-menu__dropdown">
<div>{renderCategoryLinks()}</div>
</div>
</div>
{navigationItems.slice(1).map((item) => (
<Link
className={item.key === active ? "active" : ""}
href={item.href}
key={item.label}
>
{item.label}
</Link>
))}
</nav>
{mobileOpen && (
<nav className="mobile-nav" aria-label="ناوبری موبایل">
<Link href="/" onClick={onNavigate}>
صفحه اصلی
<ChevronLeft size={17} />
</Link>
<details className="mobile-product-menu">
<summary>
محصولات
<ChevronDown size={17} />
</summary>
<div>{renderCategoryLinks()}</div>
</details>
{navigationItems.slice(1).map((item) => (
<Link href={item.href} key={item.label} onClick={onNavigate}>
{item.label}
<ChevronLeft size={17} />
</Link>
))}
</nav>
)}
</>
);
}
+8 -37
View File
@@ -3,7 +3,6 @@
import {
ArrowUpLeft,
Camera,
ChevronLeft,
CircleUserRound,
Link as LinkIcon,
Mail,
@@ -17,22 +16,14 @@ import {
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import HeaderNavigation from "./HeaderNavigation";
const assetBase = "/assets";
const navItems = [
{ label: "صفحه اصلی", href: "/" },
{ label: "محصولات", href: "/#products" },
{ label: "خدمات", href: "/#services" },
{ label: "اخبار", href: "/#news" },
{ label: "درباره ما", href: "/about-us", key: "about" },
{ label: "تماس با ما", href: "/contact-us", key: "contact" },
];
export function InteriorHeader({
active,
}: {
active?: "about" | "contact";
active?: "products" | "about" | "contact";
}) {
const [mobileOpen, setMobileOpen] = useState(false);
@@ -62,17 +53,11 @@ export function InteriorHeader({
<Link className="logo" href="/" aria-label="سامان اندیش">
<img src={`${assetBase}/KN83Drb6oI.png`} alt="سامان اندیش" />
</Link>
<nav className="desktop-nav" aria-label="ناوبری اصلی">
{navItems.map((item) => (
<Link
className={item.key === active ? "active" : ""}
href={item.href}
key={item.label}
>
{item.label}
</Link>
))}
</nav>
<HeaderNavigation
active={active}
mobileOpen={mobileOpen}
onNavigate={() => setMobileOpen(false)}
/>
<div className="header__actions">
<Link className="icon-button" href="/#smart-search" aria-label="جستجو">
<Search size={22} />
@@ -93,20 +78,6 @@ export function InteriorHeader({
</button>
</div>
</div>
{mobileOpen && (
<nav className="mobile-nav">
{navItems.map((item) => (
<Link
href={item.href}
key={item.label}
onClick={() => setMobileOpen(false)}
>
{item.label}
<ChevronLeft size={17} />
</Link>
))}
</nav>
)}
</header>
</>
);
@@ -138,7 +109,7 @@ export function InteriorFooter() {
<div className="footer__links">
<h3>دسترسی سریع</h3>
<Link href="/">صفحه اصلی</Link>
<Link href="/#products">ماشین‌آلات</Link>
<Link href="/products">ماشین‌آلات</Link>
<Link href="/about-us">درباره ما</Link>
<Link href="/contact-us">تماس با ما</Link>
</div>
+41
View File
@@ -0,0 +1,41 @@
import { ArrowUpLeft, Phone } from "lucide-react";
import Link from "next/link";
import { productPath, type Product } from "../lib/meshkee";
export default function ProductCard({ product }: { product: Product }) {
const href = productPath(product);
const title = product.nameFa || product.title;
const category =
product.categoryNameFa || product.categoryName || "ماشین‌آلات صنعتی";
const image =
product.thumbnailUrl ||
product.thumbnail ||
product.image ||
"/assets/product-quadding.jpg";
return (
<article className="product-card">
<Link className="product-card__image" href={href}>
<img src={image} alt={title} />
<span>ماشین‌آلات</span>
<i>
<ArrowUpLeft />
</i>
</Link>
<div className="product-card__body">
<small>{category}</small>
<h3>
<Link href={href}>{title}</Link>
</h3>
<p dir="ltr">{product.title}</p>
<div>
<code>{product.categoryName || "SAMAN ANDISH"}</code>
<a href="tel:+982188530256">
استعلام قیمت
<Phone size={15} />
</a>
</div>
</div>
</article>
);
}