From e44fc90d969279ed7d834f592265c234c256dfae Mon Sep 17 00:00:00 2001 From: "amirhosein.ashourloo" Date: Sun, 6 Sep 2026 14:55:58 +0330 Subject: [PATCH] Make blog cards square + clickable, add fallback article detail pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Blog card thumbnails (homepage magazine carousel, /blog listing, and the "more posts" carousel) are now square instead of 16:9. - Fallback blog cards (src/data/fallbackBlogs.ts) were previously plain, non-clickable divs since /blogs is still empty on this tenant and no real detail page existed for them. They now link to a real local /blog/{id}/{slug} page via getFallbackBlogById() — image and title are both inside the link. That page shows the real title/image/category we have plus a "coming soon" note instead of fabricating article body content. - Homepage magazine section's fallback posts now link to their own detail page instead of generically to /blog. Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 2 +- src/app/blog/[id]/[slug]/page.tsx | 51 +++++++++++++++++++++++++++++++ src/app/blog/page.tsx | 14 +++++---- src/app/page.tsx | 2 +- src/components/BlogCarousel.tsx | 2 +- src/data/fallbackBlogs.ts | 8 +++++ 6 files changed, 70 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 31e55b9..4c8f2fb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,7 @@ fallbacks should update it. | Gap | Fallback | Remove when | |---|---|---| | No prices | `src/lib/demoPricing.ts`, gated by `NEXT_PUBLIC_DEMO_PRICING=1` — deterministic price per product id | store-items exist | -| No blog posts | `src/data/fallbackBlogs.ts` — the real posts from moalem.shop/articles, shown on the homepage and `/blog` as non-clickable cards (no real detail page exists for them) | `/blogs` returns items | +| No blog posts | `src/data/fallbackBlogs.ts` — the real posts from moalem.shop/articles, shown on the homepage and `/blog` as square-thumbnail cards that link to a real local `/blog/{id}/{slug}` page (`getFallbackBlogById()`); that page shows title/category/image plus a "coming soon" note since only those three fields are real, no scraped body content | `/blogs` returns items | | No category art past slot position 8 | placeholder SVGs in `public/images/categories/{id}.svg`, generated by `scripts/gen-category-placeholders.mjs` for every category at every level, not just level-2 | the `categories` slot has an image for that position | | Wrong logo/favicon in API | `getFavicon()` is wired live (see "Brand") but the uploaded asset itself is wrong; no code fallback needed once the dashboard upload is fixed | the correct asset is uploaded | diff --git a/src/app/blog/[id]/[slug]/page.tsx b/src/app/blog/[id]/[slug]/page.tsx index 12aa647..d9d4754 100644 --- a/src/app/blog/[id]/[slug]/page.tsx +++ b/src/app/blog/[id]/[slug]/page.tsx @@ -7,6 +7,7 @@ import BlogCarousel, { type BlogCardData } from "@/components/BlogCarousel"; import { getBlogById, getBlogs, getComments, safe } from "@/lib/api"; import { getSession } from "@/lib/auth"; import { blogHref } from "@/lib/slug"; +import { FALLBACK_BLOGS, getFallbackBlogById } from "@/data/fallbackBlogs"; export async function generateMetadata({ params, @@ -14,6 +15,12 @@ export async function generateMetadata({ params: Promise<{ id: string }>; }): Promise { const { id } = await params; + + const fallback = getFallbackBlogById(id); + if (fallback) { + return { title: fallback.title, openGraph: { images: [{ url: fallback.image }] } }; + } + try { const { blog } = await getBlogById(id); const title = blog.title ?? blog.nameFa ?? ""; @@ -27,9 +34,53 @@ export async function generateMetadata({ } } +// A fallback post (see src/data/fallbackBlogs.ts) has a real id/title/image +// but no scraped body content yet — /blogs is still empty on this tenant. +// Its detail page still exists so the magazine cards can link somewhere +// real, it just shows what data we actually have instead of the full +// article body. +function FallbackBlogDetail({ + post, + others, +}: { + post: { id: string; title: string; image: string; category: string }; + others: typeof FALLBACK_BLOGS; +}) { + const morePosts: BlogCardData[] = others + .filter((p) => p.id !== post.id) + .slice(0, 4) + .map((p) => ({ id: p.id, href: blogHref(p.id, p.title), title: p.title, image: p.image, category: p.category })); + + return ( +
+ + +
+ {post.category} +

{post.title}

+ + + {post.title} + + +

+ متن کامل این مقاله به‌زودی از طریق پنل مدیریت اضافه می‌شود. +

+
+ + {morePosts.length > 0 && } +
+ ); +} + export default async function BlogDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; + const fallback = getFallbackBlogById(id); + if (fallback) { + return ; + } + let blog; try { ({ blog } = await getBlogById(id)); diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx index 1425b47..81e436a 100644 --- a/src/app/blog/page.tsx +++ b/src/app/blog/page.tsx @@ -40,17 +40,19 @@ export default async function BlogListPage({
{usingFallback ? FALLBACK_BLOGS.map((post) => ( - // No real detail page exists yet for these — see AGENTS.md fallback - // table — so the card is informational, not a link. -
- + + {post.title} {post.category}

{post.title}

-
+ )) : result.items.map((post) => { const title = post.title ?? post.nameFa ?? ""; @@ -62,7 +64,7 @@ export default async function BlogListPage({ href={blogHref(post.id, title)} className="overflow-hidden rounded-xl border border-line bg-white transition hover:border-brand/40 hover:shadow-[0_6px_20px_rgba(0,0,0,0.07)]" > - + {image && {title}} {category && ( diff --git a/src/app/page.tsx b/src/app/page.tsx index ba3b1b6..83f55ca 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -122,7 +122,7 @@ export default async function HomePage() { })) : FALLBACK_BLOGS.map((b) => ({ id: b.id, - href: "/blog", + href: blogHref(b.id, b.title), title: b.title, image: b.image, category: b.category, diff --git a/src/components/BlogCarousel.tsx b/src/components/BlogCarousel.tsx index 28c324e..68f83c8 100644 --- a/src/components/BlogCarousel.tsx +++ b/src/components/BlogCarousel.tsx @@ -50,7 +50,7 @@ export default function BlogCarousel({ posts }: { posts: BlogCardData[] }) { href={post.href} className="flex h-full flex-col overflow-hidden rounded-xl border border-line bg-white transition hover:border-brand/40 hover:shadow-[0_6px_20px_rgba(0,0,0,0.07)]" > - + {post.image && ( post.id === id); +}