From e9136ea42b2af381616212b2d21c8e4479ea4037 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Fri, 19 Jun 2026 00:35:11 +0330 Subject: [PATCH] feat(blog): enhance blog data handling with normalization and improved image management --- app/blog/[slug]/page.js | 46 +++++++++++++--------- components/blog/detail/Caption.js | 7 ++-- components/blog/relatedContent/Item.js | 12 +++--- components/blog/relatedContent/index.js | 4 +- components/blogs/Head.js | 42 ++++---------------- components/blogs/index.js | 5 ++- components/blogs/latestArticles/Article.js | 6 ++- components/blogs/title/index.js | 2 +- helper/index.js | 22 +++++++++++ services/response.js | 2 +- 10 files changed, 77 insertions(+), 71 deletions(-) diff --git a/app/blog/[slug]/page.js b/app/blog/[slug]/page.js index 12b84b3..57145d9 100644 --- a/app/blog/[slug]/page.js +++ b/app/blog/[slug]/page.js @@ -2,6 +2,9 @@ import BlogPage from "@/components/blog"; import Layout from "@/components/layout/StLayout"; import { fetchReq } from "@/lib/req"; import { getStateInfo } from "@/lib/getStateInfo"; +import { normalizeBlog, imageUrl } from "@/helper"; + +const FALLBACK_IMG = "https://www.nobat724.com/assets/images/logo.png"; export async function generateMetadata({ params }) { const { slug } = await params; @@ -10,12 +13,16 @@ export async function generateMetadata({ params }) { const siteName = matchedCity?.site_name || "نوبت 724"; try { - const blog = await fetchReq(`${API_URL}/api/v1/blog/${slug}`); + const res = await fetchReq(`${API_URL}/api/v1/blog/${slug}`); + const blog = res?.data?.data; if (!blog) return {}; const title = `${blog.title} | ${siteName}`; - const rawText = blog.body ? blog.body.replace(/<[^>]*>/g, "").trim() : ""; + const rawText = (blog.summary || blog.body || "") + .replace(/<[^>]*>/g, "") + .trim(); const description = rawText.slice(0, 160) || blog.title; + const image = blog.image_url ? imageUrl(blog.image_url) : FALLBACK_IMG; return { title, @@ -24,18 +31,16 @@ export async function generateMetadata({ params }) { title, description, type: "article", - ...(blog.created && { publishedTime: blog.created }), - images: blog.images?.[0] - ? [blog.images[0]] - : ["https://www.nobat724.com/assets/images/logo.png"], + ...(blog.created_at && { + publishedTime: new Date(blog.created_at * 1000).toISOString(), + }), + images: [image], }, twitter: { card: "summary_large_image", title, description, - images: blog.images?.[0] - ? [blog.images[0]] - : ["https://www.nobat724.com/assets/images/logo.png"], + images: [image], }, }; } catch { @@ -47,24 +52,27 @@ async function Blog({ params }) { const { slug } = await params; const API_URL = process.env.NEXT_PUBLIC_API_URL; - const blog = await fetchReq(`${API_URL}/api/v1/blog/${slug}`); + const res = await fetchReq(`${API_URL}/api/v1/blog/${slug}`); + const blog = normalizeBlog(res?.data?.data); let relatedBlogs = []; - if (blog?.tag?.[0]?.id) { - const tagId = blog.tag[0].id; - const relatedResponse = await fetchReq( - `${API_URL}/api/v1/blogs?page=1&limit=12&tag=${tagId}` - ); - relatedBlogs = relatedResponse?.blogs || []; - } + const relatedResponse = await fetchReq( + `${API_URL}/api/v1/blogs?page=1&limit=6` + ); + relatedBlogs = (relatedResponse?.data || []) + .filter((b) => b.uuid !== blog?.uuid) + .slice(0, 4) + .map(normalizeBlog); const jsonLd = blog ? { "@context": "https://schema.org", "@type": "Article", headline: blog.title, - ...(blog.images?.[0] && { image: blog.images[0] }), - ...(blog.created && { datePublished: blog.created }), + ...(blog.images?.[0]?.url && { image: imageUrl(blog.images[0].url) }), + ...(blog.created && { + datePublished: new Date(blog.created * 1000).toISOString(), + }), ...(blog.author && { author: { "@type": "Person", name: blog.author } }), } : null; diff --git a/components/blog/detail/Caption.js b/components/blog/detail/Caption.js index ffc0a28..4a3f3a9 100644 --- a/components/blog/detail/Caption.js +++ b/components/blog/detail/Caption.js @@ -1,16 +1,17 @@ import React from "react"; import Image from "next/image"; import { sanitizeHtml } from "@/lib/sanitize"; +import { imageUrl } from "@/helper"; function Caption({ data }) { - const imageUrl = data?.images?.[0]?.url; + const cover = data?.images?.[0]?.url ? imageUrl(data.images[0].url) : ""; return ( <> - {imageUrl && imageUrl.trim() !== "" && ( + {cover && cover.trim() !== "" && (
{data?.title - {imageUrl && imageUrl.trim() !== "" ? ( + {cover && cover.trim() !== "" ? ( {data.title ) : ( diff --git a/components/blog/relatedContent/index.js b/components/blog/relatedContent/index.js index d5b60e9..9dcb08c 100644 --- a/components/blog/relatedContent/index.js +++ b/components/blog/relatedContent/index.js @@ -11,9 +11,9 @@ function RelatedContent({ data, loading }) {
diff --git a/components/blogs/Head.js b/components/blogs/Head.js index 7d5481d..e904daf 100644 --- a/components/blogs/Head.js +++ b/components/blogs/Head.js @@ -1,39 +1,13 @@ "use client"; -import { useState, useEffect } from "react"; -import CustomLoading from "@/app/component/loading/Custom"; -import TextLoading from "@/app/component/loading/Text"; -import Link from "next/link"; -import { request } from "@/services/response"; +// نمایش «بلاگ‌های برتر» فعلاً غیرفعال است (endpoint /api/v1/blogs/top در بک‌اند وجود ندارد). +// markup زیر برای فعال‌سازی بعدی نگه داشته شده؛ تا آن زمان کامپوننت چیزی رندر نمی‌کند. function Head() { - const [topBlogs, setTopBlogs] = useState([]); - const [isLoading, setIsLoading] = useState(true); + return null; +} - useEffect(() => { - const fetchTopBlogs = async () => { - try { - setIsLoading(true); - const response = await request.getTopBlogs(); - setTopBlogs(response || []); - } catch (error) { - console.error("Error fetching top blogs:", error); - setTopBlogs([]); - } finally { - setIsLoading(false); - } - }; - - fetchTopBlogs(); - }, []); - - if (isLoading || topBlogs.length === 0) { - return null; - } - - const [firstBlog, secondBlog, thirdBlog, fourthBlog] = topBlogs; - - return ( - <>{/* +/* eslint-disable */ +/*
{firstBlog && ( @@ -104,8 +78,6 @@ function Head() { )}
- */} - ); -} +*/ export default Head; diff --git a/components/blogs/index.js b/components/blogs/index.js index 0bb5e45..cdfab08 100644 --- a/components/blogs/index.js +++ b/components/blogs/index.js @@ -5,6 +5,7 @@ import Head from "./Head"; import Title from "./title"; import LatestArticles from "./latestArticles"; import { request } from "@/services/response"; +import { normalizeBlog } from "@/helper"; function BlogsPage() { const [blogs, setBlogs] = useState([]); @@ -25,8 +26,8 @@ function BlogsPage() { params.tag = selectedTag; } const response = await request.getBlogs(params); - setBlogs(response.blogs || []); - setTotalPages(response.page?.totalPages || 1); + setBlogs((response?.data || []).map(normalizeBlog)); + setTotalPages(response?.meta?.totalPages || 1); } catch (error) { console.error("Error fetching blogs:", error); setBlogs([]); diff --git a/components/blogs/latestArticles/Article.js b/components/blogs/latestArticles/Article.js index c0f4849..5e76d94 100644 --- a/components/blogs/latestArticles/Article.js +++ b/components/blogs/latestArticles/Article.js @@ -2,10 +2,12 @@ import CustomLoading from "@/app/component/loading/Custom"; import TextLoading from "@/app/component/loading/Text"; import Image from "next/image"; import Link from "next/link"; -import { convertTimestampToJalali } from "@/helper"; +import { convertTimestampToJalali, imageUrl } from "@/helper"; function Article({ data }) { - const imageSrc = data.images?.[0]?.url || "/assets/images/default-blog.jpg"; + const imageSrc = data.images?.[0]?.url + ? imageUrl(data.images[0].url, "/assets/images/cover-blog-1.png") + : "/assets/images/cover-blog-1.png"; const createdDate = data.created ? convertTimestampToJalali(data.created) : ""; return ( diff --git a/components/blogs/title/index.js b/components/blogs/title/index.js index 3b2d2d5..a212943 100644 --- a/components/blogs/title/index.js +++ b/components/blogs/title/index.js @@ -15,7 +15,7 @@ function Title({ onTagChange }) { limit: 100, }; const response = await request.getBlogTags(params); - setTags(response.data || []); + setTags(response?.data?.data || []); } catch (error) { console.error("Error fetching tags:", error); setTags([]); diff --git a/helper/index.js b/helper/index.js index 08ddbe0..27da1ab 100644 --- a/helper/index.js +++ b/helper/index.js @@ -12,6 +12,28 @@ export const numberToArStyle = (num) => num?.toLocaleString("ar-AE") || ""; export const isActiveURL = (link, name) => link === name; +// نگاشت پاسخ API بلاگ (image_url/tags/created_at/body/author-object) به شکلی که +// کامپوننت‌های بلاگ انتظار دارند (images[{url}]/tag[{name}]/created/body.value/author-string). +export const normalizeBlog = (blog) => { + if (!blog) return null; + return { + ...blog, + images: blog.image_url ? [{ url: blog.image_url }] : [], + tag: Array.isArray(blog.tags) + ? blog.tags.map((t) => (typeof t === "string" ? { name: t } : t)) + : [], + created: blog.created_at ?? blog.created ?? null, + body: + blog.body && typeof blog.body === "object" + ? blog.body + : { value: blog.body ?? "" }, + author: + blog.author && typeof blog.author === "object" + ? blog.author.name + : blog.author ?? null, + }; +}; + // تبدیل مسیر نسبی تصویر (/uploads/...) به URL کامل backend. // مسیرهای absolute (http...) و asset های محلی (/assets، /default) دست‌نخورده می‌مانند. export const imageUrl = (url, fallback = "/assets/images/user.png") => { diff --git a/services/response.js b/services/response.js index 082e47a..fe212ef 100644 --- a/services/response.js +++ b/services/response.js @@ -117,7 +117,7 @@ export const request = { }, }), getBlogTags: (params) => - api.get("api/v1/categorys/tag", { + api.get("api/v1/tags", { params, headers: { Authorization: "",