Polish product/blog detail pages: gallery, tabs, Persian names, image fixes
- Product detail: interactive image gallery (thumbnails, prev/next,
hover-zoom), tabbed spec/review/comments section matching the site's
standard pattern, variations/options display, and an availability
label instead of duplicating the contact-store message.
- Fix product.images normalization — the API returns {url} media objects,
not plain strings, so galleries were silently rendering empty.
- Prefer the Persian name (nameFa/productNameFa) everywhere a product
name is shown.
- Product cards: padded, rounded images instead of edge-to-edge with a
dashed divider.
- Blog: wire the homepage carousel to real posts (was static sample
data), drop the detail page's separate cover image, and make images
embedded in article/description HTML fill their container with
rounded corners.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2b5f4e437b
commit
7fb1f5a60f
@@ -0,0 +1,81 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { EmptyState } from "@/components/ui/EmptyState";
|
||||
import type { TechnicalInfoField } from "@/lib/meshkee";
|
||||
|
||||
type Tab = "specs" | "review" | "comments";
|
||||
|
||||
const TABS: { id: Tab; label: string }[] = [
|
||||
{ id: "specs", label: "مشخصات فنی" },
|
||||
{ id: "review", label: "نقد و بررسی" },
|
||||
{ id: "comments", label: "نظرات کاربران" },
|
||||
];
|
||||
|
||||
export function ProductTabs({
|
||||
technicalInfo,
|
||||
descriptionHtml,
|
||||
}: {
|
||||
technicalInfo: TechnicalInfoField[];
|
||||
descriptionHtml?: string;
|
||||
}) {
|
||||
const [active, setActive] = useState<Tab>("specs");
|
||||
const activeLabel = TABS.find((t) => t.id === active)?.label ?? "";
|
||||
|
||||
return (
|
||||
<div className="mt-10">
|
||||
<div className="flex items-center gap-8 border-b border-border">
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
type="button"
|
||||
onClick={() => setActive(tab.id)}
|
||||
className={`relative pb-4 text-sm transition-colors ${
|
||||
active === tab.id ? "font-bold text-foreground" : "text-muted hover:text-red"
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
{active === tab.id && (
|
||||
<span className="absolute inset-x-0 -bottom-px h-0.5 bg-foreground" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="pt-8">
|
||||
<h2 className="mb-5 flex items-center gap-2 text-lg font-bold">
|
||||
<span className="h-2 w-2 shrink-0 rounded-full bg-red" aria-hidden />
|
||||
{activeLabel}
|
||||
</h2>
|
||||
|
||||
{active === "specs" &&
|
||||
(technicalInfo.length === 0 ? (
|
||||
<EmptyState message="هنوز مشخصات فنیای برای این محصول ثبت نشده است." />
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{technicalInfo.map((field) => (
|
||||
<div key={field.label} className="rounded-xl bg-background p-4">
|
||||
<p className="mb-1 text-sm font-bold">{field.label} :</p>
|
||||
<p className="text-sm text-muted">{field.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{active === "review" &&
|
||||
(descriptionHtml ? (
|
||||
<div
|
||||
className="prose prose-sm max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: descriptionHtml }}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState message="هنوز نقد و بررسیای برای این محصول ثبت نشده است." />
|
||||
))}
|
||||
|
||||
{active === "comments" && (
|
||||
<EmptyState message="هنوز نظری برای این محصول ثبت نشده است." />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user