feat: Implement blog API service and refactor components for improved data fetching

This commit is contained in:
Wini_Fy
2026-02-04 15:33:02 +07:00
parent d46c420aaf
commit 9a71d39ebf
16 changed files with 790 additions and 149 deletions

View File

@@ -9,19 +9,25 @@ interface NewsDetailsContentProps {
}
export default function NewsDetailsContent({ post }: NewsDetailsContentProps) {
// Get comments from post (already included in API response)
// Lấy comments từ post (đã được bao gồm trong API response)
const postComments = post.comments || [];
// Get base URL for EditorJS images
// Lấy base URL cho EditorJS images
const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001";
// Convert EditorJS content to HTML
// URL tuyệt đối của bài viết để share lên mạng xã hội
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000";
const postUrl = `${siteUrl}/blog/${post.slug}`;
const encodedPostUrl = encodeURIComponent(postUrl);
const encodedTitle = encodeURIComponent(post.title);
// Chuyển đổi EditorJS content sang HTML
const renderContent = () => {
const html = editorjsToHtml(post.content, baseUrl);
return { __html: html };
};
// Convert EditorJS contentAfterQuote to HTML
// Chuyển đổi EditorJS contentAfterQuote sang HTML
const renderContentAfterQuote = () => {
const html = editorjsToHtml(post.contentAfterQuote, baseUrl);
return { __html: html };
@@ -49,13 +55,13 @@ export default function NewsDetailsContent({ post }: NewsDetailsContentProps) {
</li>
</ul>
<h2>{post.title}</h2>
<div dangerouslySetInnerHTML={renderContent()} />
<div className="editorjs-render" dangerouslySetInnerHTML={renderContent()} />
{/* Gallery Images */}
{/* Hình ảnh gallery */}
{post.galleryImages && post.galleryImages.length > 0 && (
<div className="row g-4 mt-4">
<div className="row g-4 gallery-images-row">
{post.galleryImages.map((image, index) => (
<div key={index} className={post.galleryImages!.length === 1 ? "col-12" : "col-lg-6"}>
<div key={index} className={post.galleryImages!.length === 1 ? "col-12" : "col-lg-6 gallery-item"}>
<div className="thumb">
<img src={getCmsImageUrl(image)} alt={`${post.title} - Image ${index + 1}`} />
</div>
@@ -71,18 +77,21 @@ export default function NewsDetailsContent({ post }: NewsDetailsContentProps) {
</div>
)}
{/* Content After Quote */}
{/* Nội dung sau Quote */}
{post.contentAfterQuote && (
<div dangerouslySetInnerHTML={renderContentAfterQuote()} />
<div
className="editorjs-render"
dangerouslySetInnerHTML={renderContentAfterQuote()}
/>
)}
{/* Tags and Social Share */}
{/* Tags Social Share */}
<div className="row tag-share-wrap mt-4 mb-5">
<div className="col-lg-8 col-12">
<div className="tagcloud">
<span>Tags:</span>
{post.tags.map((tagName) => {
// Generate slug from tag name (Vietnamese-friendly)
// Tạo slug từ tên tag (hỗ trợ tiếng Việt)
const tagSlug = toSlug(tagName);
return (
<Link key={tagName} href={`/blog/tag/${tagSlug}`}>
@@ -94,17 +103,37 @@ export default function NewsDetailsContent({ post }: NewsDetailsContentProps) {
</div>
<div className="col-lg-4 col-12 mt-3 mt-lg-0 text-lg-end">
<div className="social-share">
<a href="#" aria-label="Share on Twitter">
<a
href={`https://twitter.com/intent/tweet?url=${encodedPostUrl}&text=${encodedTitle}`}
target="_blank"
rel="noopener noreferrer"
aria-label="Share on Twitter"
>
<i className="fab fa-twitter"></i>
</a>
<a href="#" aria-label="Share on YouTube">
<i className="fa-brands fa-youtube"></i>
<a
href={`https://www.facebook.com/sharer/sharer.php?u=${encodedPostUrl}`}
target="_blank"
rel="noopener noreferrer"
aria-label="Share on Facebook"
>
<i className="fab fa-facebook-f"></i>
</a>
<a href="#" aria-label="Share on LinkedIn">
<a
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodedPostUrl}`}
target="_blank"
rel="noopener noreferrer"
aria-label="Share on LinkedIn"
>
<i className="fab fa-linkedin-in"></i>
</a>
<a href="#" aria-label="Share on Facebook">
<i className="fab fa-facebook-f"></i>
<a
href={postUrl}
target="_blank"
rel="noopener noreferrer"
aria-label="Open blog post"
>
<i className="fa-solid fa-link"></i>
</a>
</div>
</div>