Add Meshkee product catalog navigation and details
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
00126e5777
commit
34ce4f6925
+82
-54
@@ -5,14 +5,13 @@ import {
|
||||
ArrowUpLeft,
|
||||
BriefcaseBusiness,
|
||||
Camera,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
CircleUserRound,
|
||||
FlaskConical,
|
||||
Gauge,
|
||||
Globe2,
|
||||
Link,
|
||||
Link as LinkIcon,
|
||||
Mail,
|
||||
MapPin,
|
||||
Menu,
|
||||
@@ -25,13 +24,18 @@ import {
|
||||
Sparkles,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import NextLink from "next/link";
|
||||
import type { FormEvent } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
cssAspectRatio,
|
||||
fetchHomeStaticImageSlots,
|
||||
fetchLatestProducts,
|
||||
productPath,
|
||||
type Product,
|
||||
type StaticImageSlot,
|
||||
} from "./lib/meshkee";
|
||||
import HeaderNavigation from "./components/HeaderNavigation";
|
||||
|
||||
const assetBase = "/assets";
|
||||
|
||||
@@ -86,41 +90,78 @@ const categories = [
|
||||
},
|
||||
];
|
||||
|
||||
const products = [
|
||||
type DisplayProduct = {
|
||||
id: string;
|
||||
title: string;
|
||||
en: string;
|
||||
category: string;
|
||||
image: string;
|
||||
code: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
const fallbackProducts: DisplayProduct[] = [
|
||||
{
|
||||
id: 1,
|
||||
id: "fallback-1",
|
||||
title: "دستگاه کوادینگ ۶۰۰ میلیمتر",
|
||||
en: "600mm Quadding Machine | QUA-21 | LESMO",
|
||||
category: "دستگاه تابنده",
|
||||
image: `${assetBase}/product-quadding.jpg`,
|
||||
code: "LESMO / QUA-21",
|
||||
href: "#products",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
id: "fallback-2",
|
||||
title: "بریدر ۱۶ کریر",
|
||||
en: "16 Carriers Braider | 16N4 | SPIRKA",
|
||||
category: "دستگاه بافنده",
|
||||
image: `${assetBase}/product-braider.jpg`,
|
||||
code: "SPIRKA / 16N4",
|
||||
href: "#products",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
id: "fallback-3",
|
||||
title: "خط کشش ۰.۵–۰.۱۵ میلیمتر",
|
||||
en: "0.15–0.5 SQmm Drawing Line | TRB3 | SAMP",
|
||||
category: "دستگاه کشش",
|
||||
image: `${assetBase}/product-drawing-line.jpg`,
|
||||
code: "SAMP / TRB3",
|
||||
href: "#products",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
id: "fallback-4",
|
||||
title: "پیآف ۶۰۰ میلیمتر",
|
||||
en: "600mm Pay Off | TAP D-0800 | KDM",
|
||||
category: "دستگاه پیآف",
|
||||
image: `${assetBase}/product-payoff.jpg`,
|
||||
code: "KDM / TAP D-0800",
|
||||
href: "#products",
|
||||
},
|
||||
];
|
||||
|
||||
function toDisplayProduct(product: Product, index: number): DisplayProduct {
|
||||
const fallback = fallbackProducts[index % fallbackProducts.length];
|
||||
const brand =
|
||||
typeof product.brand === "string"
|
||||
? product.brand
|
||||
: product.brand?.nameFa || product.brand?.name;
|
||||
|
||||
return {
|
||||
id: product.id,
|
||||
title: product.nameFa || product.title,
|
||||
en: product.title,
|
||||
category:
|
||||
product.categoryNameFa || product.categoryName || "ماشینآلات صنعتی",
|
||||
image:
|
||||
product.thumbnailUrl ||
|
||||
product.thumbnail ||
|
||||
product.image ||
|
||||
fallback.image,
|
||||
code: brand || product.categoryName || "SAMAN ANDISH",
|
||||
href: productPath(product),
|
||||
};
|
||||
}
|
||||
|
||||
const news = [
|
||||
{
|
||||
title: "شرکت مهندسی، مشاوره و بازرگانی HService",
|
||||
@@ -179,6 +220,8 @@ export default function Home() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
const [staticImageSlots, setStaticImageSlots] = useState<StaticImageSlot[]>([]);
|
||||
const [displayProducts, setDisplayProducts] =
|
||||
useState<DisplayProduct[]>(fallbackProducts);
|
||||
const searchInput = useRef<HTMLInputElement>(null);
|
||||
|
||||
const slotsByKey = useMemo(
|
||||
@@ -242,6 +285,14 @@ export default function Home() {
|
||||
// Keep the local image fallbacks when the API is unavailable.
|
||||
});
|
||||
|
||||
fetchLatestProducts(4, controller.signal)
|
||||
.then((items) => {
|
||||
if (items.length) setDisplayProducts(items.map(toDisplayProduct));
|
||||
})
|
||||
.catch(() => {
|
||||
// Keep the local product fallbacks when the API is unavailable.
|
||||
});
|
||||
|
||||
return () => controller.abort();
|
||||
}, []);
|
||||
|
||||
@@ -258,14 +309,14 @@ export default function Home() {
|
||||
|
||||
const results = useMemo(() => {
|
||||
const normalized = query.trim().toLowerCase();
|
||||
if (!normalized) return products.slice(0, 3);
|
||||
return products.filter((product) =>
|
||||
if (!normalized) return displayProducts.slice(0, 3);
|
||||
return displayProducts.filter((product) =>
|
||||
[product.title, product.en, product.category, product.code]
|
||||
.join(" ")
|
||||
.toLowerCase()
|
||||
.includes(normalized),
|
||||
);
|
||||
}, [query]);
|
||||
}, [displayProducts, query]);
|
||||
|
||||
const focusSearch = () => {
|
||||
document.getElementById("smart-search")?.scrollIntoView({
|
||||
@@ -310,19 +361,11 @@ export default function Home() {
|
||||
<a className="logo" href="#" aria-label="سامان اندیش">
|
||||
<img src={`${assetBase}/KN83Drb6oI.png`} alt="سامان اندیش" />
|
||||
</a>
|
||||
<nav className="desktop-nav" aria-label="ناوبری اصلی">
|
||||
<a className="active" href="#">
|
||||
صفحه اصلی
|
||||
</a>
|
||||
<a href="#products">
|
||||
محصولات
|
||||
<ChevronDown size={14} />
|
||||
</a>
|
||||
<a href="#services">خدمات</a>
|
||||
<a href="#news">اخبار</a>
|
||||
<a href="/about-us">درباره ما</a>
|
||||
<a href="/contact-us">تماس با ما</a>
|
||||
</nav>
|
||||
<HeaderNavigation
|
||||
active="home"
|
||||
mobileOpen={mobileOpen}
|
||||
onNavigate={() => setMobileOpen(false)}
|
||||
/>
|
||||
<div className="header__actions">
|
||||
<button className="icon-button" onClick={focusSearch} aria-label="جستجو">
|
||||
<Search size={22} />
|
||||
@@ -343,23 +386,6 @@ export default function Home() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{mobileOpen && (
|
||||
<nav className="mobile-nav">
|
||||
{[
|
||||
["صفحه اصلی", "#"],
|
||||
["محصولات", "#products"],
|
||||
["خدمات", "#services"],
|
||||
["اخبار", "#news"],
|
||||
["درباره ما", "/about-us"],
|
||||
["تماس با ما", "/contact-us"],
|
||||
].map(([label, href]) => (
|
||||
<a key={label} href={href} onClick={() => setMobileOpen(false)}>
|
||||
{label}
|
||||
<ChevronLeft size={17} />
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<section className="hero">
|
||||
@@ -390,10 +416,10 @@ export default function Home() {
|
||||
<h1>{activeHero.title}</h1>
|
||||
<p className="hero__description">{activeHero.text}</p>
|
||||
<div className="hero__buttons">
|
||||
<a className="button button--red" href="#products">
|
||||
<NextLink className="button button--red" href="/products">
|
||||
مشاهده محصولات
|
||||
<ArrowLeft size={19} />
|
||||
</a>
|
||||
</NextLink>
|
||||
<a className="button button--glass" href="/about-us">
|
||||
درباره سامان اندیش
|
||||
</a>
|
||||
@@ -497,7 +523,7 @@ export default function Home() {
|
||||
</div>
|
||||
{results.length ? (
|
||||
results.map((product) => (
|
||||
<a href="#products" key={product.id}>
|
||||
<NextLink href={product.href} key={product.id}>
|
||||
<img src={product.image} alt={product.title} />
|
||||
<div>
|
||||
<strong>{product.title}</strong>
|
||||
@@ -505,7 +531,7 @@ export default function Home() {
|
||||
</div>
|
||||
<span>{product.category}</span>
|
||||
<ChevronLeft size={18} />
|
||||
</a>
|
||||
</NextLink>
|
||||
))
|
||||
) : (
|
||||
<div className="empty-result">
|
||||
@@ -556,25 +582,27 @@ export default function Home() {
|
||||
<section className="products section-gray" id="products">
|
||||
<div className="container">
|
||||
<div className="section-heading-row">
|
||||
<SectionTitle eyebrow="SPECIAL MACHINERY" title="ماشینآلات منتخب" />
|
||||
<a className="text-link" href="#">
|
||||
<SectionTitle eyebrow="LATEST MACHINERY" title="جدیدترین ماشینآلات" />
|
||||
<NextLink className="text-link" href="/products">
|
||||
مشاهده همه محصولات
|
||||
<ArrowLeft size={18} />
|
||||
</a>
|
||||
</NextLink>
|
||||
</div>
|
||||
<div className="product-grid">
|
||||
{products.map((product) => (
|
||||
{displayProducts.map((product) => (
|
||||
<article className="product-card" key={product.id}>
|
||||
<a className="product-card__image" href="#">
|
||||
<NextLink className="product-card__image" href={product.href}>
|
||||
<img src={product.image} alt={product.title} />
|
||||
<span>فروش ویژه</span>
|
||||
<span>جدیدترین</span>
|
||||
<i>
|
||||
<ArrowUpLeft />
|
||||
</i>
|
||||
</a>
|
||||
</NextLink>
|
||||
<div className="product-card__body">
|
||||
<small>{product.category}</small>
|
||||
<h3>{product.title}</h3>
|
||||
<h3>
|
||||
<NextLink href={product.href}>{product.title}</NextLink>
|
||||
</h3>
|
||||
<p dir="ltr">{product.en}</p>
|
||||
<div>
|
||||
<code>{product.code}</code>
|
||||
@@ -791,7 +819,7 @@ export default function Home() {
|
||||
<Camera />
|
||||
</a>
|
||||
<a href="#" aria-label="لینکدین">
|
||||
<Link />
|
||||
<LinkIcon />
|
||||
</a>
|
||||
<a href="#" aria-label="تلگرام">
|
||||
<MessageCircle />
|
||||
@@ -801,7 +829,7 @@ export default function Home() {
|
||||
<div className="footer__links">
|
||||
<h3>دسترسی سریع</h3>
|
||||
<a href="#">صفحه اصلی</a>
|
||||
<a href="#products">ماشینآلات</a>
|
||||
<NextLink href="/products">ماشینآلات</NextLink>
|
||||
<a href="#news">اخبار و مقالات</a>
|
||||
<a href="/about-us">درباره ما</a>
|
||||
<a href="/contact-us">تماس با ما</a>
|
||||
|
||||
Reference in New Issue
Block a user