Connect product/blog pages, mega menu, and instant search to Meshkee backend
Add /products (list + detail) and /blog (list + detail) routes backed by the real Meshkee API, a real category-hierarchy mega menu (level 2/3 only), debounced instant search proxied through a same-origin API route, and tel:-link fallbacks on product cards when no price is set. Also fixes Product price normalization to read the real API shape instead of a guessed one, and hardens product linking against catalog sources that don't expose a slug. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
1f81236d0a
commit
2b5f4e437b
@@ -0,0 +1,97 @@
|
||||
import { Placeholder } from "@/components/ui/Placeholder";
|
||||
import { EmptyState } from "@/components/ui/EmptyState";
|
||||
import { getBlogs } from "@/lib/meshkee";
|
||||
|
||||
const PAGE_SIZE = 12;
|
||||
|
||||
function formatDate(iso?: string) {
|
||||
if (!iso) return "";
|
||||
return new Intl.DateTimeFormat("fa-IR", { dateStyle: "long" }).format(new Date(iso));
|
||||
}
|
||||
|
||||
export default async function BlogListPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ page?: string }>;
|
||||
}) {
|
||||
const params = await searchParams;
|
||||
const page = Math.max(1, Number(params.page ?? 1) || 1);
|
||||
const { items, total } = await getBlogs({ page, pageSize: PAGE_SIZE });
|
||||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-10">
|
||||
<h1 className="mb-6 text-2xl font-bold text-red-dark">وبلاگ</h1>
|
||||
|
||||
{items.length === 0 ? (
|
||||
<EmptyState message="هنوز مطلبی ثبت نشده است." />
|
||||
) : (
|
||||
<>
|
||||
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{items.map((post) => (
|
||||
<a
|
||||
key={post.id}
|
||||
href={`/blog/${post.slug}`}
|
||||
className="group overflow-hidden rounded-xl border border-border bg-surface transition-shadow hover:shadow-md"
|
||||
>
|
||||
<Placeholder
|
||||
src={post.image ?? "/images/placeholder.svg"}
|
||||
alt={post.title}
|
||||
width="100%"
|
||||
height="180px"
|
||||
className="w-full rounded-none border-0"
|
||||
/>
|
||||
<div className="p-4">
|
||||
<div className="mb-2 flex items-center gap-2 text-[11px] text-muted">
|
||||
{post.categoryName && <span>{post.categoryName}</span>}
|
||||
{post.publishedAt && <span>{formatDate(post.publishedAt)}</span>}
|
||||
</div>
|
||||
<h3 className="mb-2 line-clamp-2 text-sm font-bold leading-relaxed">
|
||||
{post.title}
|
||||
</h3>
|
||||
{post.abstract && (
|
||||
<p className="line-clamp-2 text-xs leading-relaxed text-muted">
|
||||
{post.abstract}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<div className="mt-8 flex items-center justify-center gap-3">
|
||||
{page > 1 ? (
|
||||
<a
|
||||
href={`/blog?page=${page - 1}`}
|
||||
className="rounded-lg border border-border px-4 py-2 text-sm text-muted transition-colors hover:border-red hover:text-red"
|
||||
>
|
||||
قبلی
|
||||
</a>
|
||||
) : (
|
||||
<span className="rounded-lg border border-border px-4 py-2 text-sm text-muted/40">
|
||||
قبلی
|
||||
</span>
|
||||
)}
|
||||
<span className="text-sm text-muted">
|
||||
صفحه {page} از {totalPages}
|
||||
</span>
|
||||
{page < totalPages ? (
|
||||
<a
|
||||
href={`/blog?page=${page + 1}`}
|
||||
className="rounded-lg border border-border px-4 py-2 text-sm text-muted transition-colors hover:border-red hover:text-red"
|
||||
>
|
||||
بعدی
|
||||
</a>
|
||||
) : (
|
||||
<span className="rounded-lg border border-border px-4 py-2 text-sm text-muted/40">
|
||||
بعدی
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user