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:
amirhosein.ashourloo
2026-08-18 19:38:54 +03:30
co-authored by Claude Sonnet 5
parent 2b5f4e437b
commit 7fb1f5a60f
11 changed files with 351 additions and 168 deletions
+48 -38
View File
@@ -1,9 +1,15 @@
import { Placeholder } from "@/components/ui/Placeholder";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { Carousel } from "@/components/ui/Carousel";
import { blogPosts } from "@/data/sample";
import { EmptyState } from "@/components/ui/EmptyState";
import type { BlogSummary } from "@/lib/meshkee";
export function BlogCarousel() {
function formatDate(iso?: string) {
if (!iso) return "";
return new Intl.DateTimeFormat("fa-IR", { dateStyle: "long" }).format(new Date(iso));
}
export function BlogCarousel({ posts }: { posts: BlogSummary[] }) {
return (
<section className="py-10 md:py-14">
<div className="mx-auto max-w-7xl px-4">
@@ -12,42 +18,46 @@ export function BlogCarousel() {
enTitle="Latest Blog"
action="وبلاگ"
/>
<Carousel ariaLabel="کاروسل وبلاگ">
{blogPosts.map((post) => (
<article
key={post.id}
className="w-[280px] shrink-0 overflow-hidden rounded-xl border border-border bg-surface transition-shadow hover:shadow-md md:w-[300px]"
>
<Placeholder
src={post.image}
alt={post.title}
width="100%"
height="160px"
label="300×160"
className="w-full rounded-none border-0 border-b border-dashed"
/>
<div className="p-4">
<div className="mb-2 flex items-center gap-2 text-[11px] text-muted">
<span>{post.date}</span>
<span>•</span>
<span>{post.readTime} مطالعه</span>
</div>
<h3 className="mb-2 line-clamp-2 text-sm font-bold leading-relaxed">
{post.title}
</h3>
<p className="line-clamp-2 text-xs leading-relaxed text-muted">
{post.excerpt}
</p>
<button
type="button"
className="mt-3 text-xs font-medium text-red transition-colors hover:text-red-dark"
>
ادامه مطلب ←
</button>
</div>
</article>
))}
</Carousel>
{posts.length === 0 ? (
<EmptyState message="هنوز مطلبی ثبت نشده است." />
) : (
<Carousel ariaLabel="کاروسل وبلاگ">
{posts.map((post) => (
<article
key={post.id}
className="w-[280px] shrink-0 overflow-hidden rounded-xl border border-border bg-surface transition-shadow hover:shadow-md md:w-[300px]"
>
<a href={`/blog/${post.slug}`}>
<div className="p-[10px]">
<Placeholder
src={post.image ?? "/images/placeholder.svg"}
alt={post.title}
width="100%"
fit="contain"
className="aspect-square w-full rounded-lg border-0"
/>
</div>
<div className="p-4 pt-0">
{post.publishedAt && (
<div className="mb-2 text-[11px] text-muted">{formatDate(post.publishedAt)}</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>
)}
<span className="mt-3 block text-xs font-medium text-red">
ادامه مطلب ←
</span>
</div>
</a>
</article>
))}
</Carousel>
)}
</div>
</section>
);