Files
gg-backend/frontend/src/pages/BlogDetails.tsx
T

205 lines
4.2 KiB
TypeScript
Raw Normal View History

2026-04-14 16:04:44 +05:30
import React, {useEffect, useState} from "react";
import {useParams, useNavigate} from "react-router-dom";
import {Button} from "@/components/ui/button";
import {getBlogByIdApi} from "@/api/blog";
2026-04-16 16:38:16 +05:30
/* ---------------- LIST RENDERER ---------------- */
const renderList = (items, style) => {
// ✅ Checklist
if (style === "checklist") {
return (
<div className="mb-4 space-y-2">
{items.map((item, i) => (
<div key={i} className="flex items-center gap-2">
<input
type="checkbox"
checked={item.meta?.checked || false}
readOnly
/>
<span
className={item.meta?.checked ? "line-through opacity-60" : ""}
dangerouslySetInnerHTML={{
__html: item.content || "",
}}
/>
</div>
))}
</div>
);
}
// ✅ Ordered / Unordered
const ListTag = style === "ordered" ? "ol" : "ul";
return (
<ListTag
className={`pl-6 mb-4 ${
style === "ordered" ? "list-decimal" : "list-disc"
}`}
>
{items
.filter((item) => item.content)
.map((item, i) => (
<li key={i}>
<span
dangerouslySetInnerHTML={{
__html: item.content,
}}
/>
{item.items?.length > 0 && renderList(item.items, style)}
</li>
))}
</ListTag>
);
};
/* ---------------- BLOCK RENDERER ---------------- */
const renderBlock = (block, index) => {
switch (block.type) {
case "paragraph":
return (
<p
key={index}
className="mb-4 leading-7 text-gray-800"
dangerouslySetInnerHTML={{__html: block.data.text}}
/>
);
case "header":
return (
<h2
key={index}
className="text-2xl font-semibold mb-4"
dangerouslySetInnerHTML={{__html: block.data.text}}
/>
);
case "image":
return (
<img
key={index}
src={block.data.file?.url}
alt={block.data.caption || "blog-image"}
className="w-full rounded-lg mb-4"
/>
);
case "list":
return (
<div key={index}>{renderList(block.data.items, block.data.style)}</div>
);
case "quote":
return (
<blockquote
key={index}
className="border-l-4 border-gray-300 pl-4 italic my-4 text-gray-600"
>
{block.data.text}
</blockquote>
);
case "code":
return (
<pre
key={index}
className="bg-gray-100 p-4 rounded-md overflow-x-auto text-sm mb-4"
>
<code>{block.data.code}</code>
</pre>
);
case "table":
return (
<div key={index} className="overflow-x-auto mb-6">
<table className="w-full border border-gray-200">
<tbody>
{block.data.content.map((row, i) => (
<tr key={i}>
{row.map((cell, j) => (
<td
key={j}
className="border px-3 py-2"
dangerouslySetInnerHTML={{
__html: cell,
}}
/>
))}
</tr>
))}
</tbody>
</table>
</div>
);
case "delimiter":
return <hr key={index} className="my-6 border-gray-300" />;
default:
return null;
}
};
/* ---------------- MAIN COMPONENT ---------------- */
2026-04-14 16:04:44 +05:30
const BlogDetail = () => {
const {id} = useParams();
const navigate = useNavigate();
2026-04-16 16:38:16 +05:30
const [blog, setBlog] = useState(null);
2026-04-14 16:04:44 +05:30
2026-04-16 16:38:16 +05:30
const fetchBlog = async () => {
2026-04-14 16:04:44 +05:30
try {
2026-04-16 16:38:16 +05:30
const res = await getBlogByIdApi(Number(id));
console.log({res});
setBlog(res);
} catch (err) {
console.error("Error fetching blog", err);
2026-04-14 16:04:44 +05:30
}
};
useEffect(() => {
2026-04-16 16:38:16 +05:30
fetchBlog();
}, [id]);
2026-04-14 16:04:44 +05:30
if (!blog) {
2026-04-16 16:38:16 +05:30
return <p className="mt-40 text-center">Loading...</p>;
2026-04-14 16:04:44 +05:30
}
return (
2026-04-16 16:38:16 +05:30
<div className="mx-auto flex flex-col ">
{/* Back Button */}
<Button
variant="ghost"
className="mb-4 w-fit text-black px-0"
onClick={() => navigate(-1)}
>
Back
</Button>
{/* Title */}
<h1 className="text-3xl md:text-5xl font-bold mb-2">{blog.title}</h1>
{/* Meta */}
<p className="text-gray-500 mb-4">
{blog.writer} {new Date(blog.createdAt).toLocaleDateString()}
</p>
{/* Image (only if exists) */}
{blog.image?.trim() && (
<img
src={blog.image}
alt="blog"
className="w-full h-[220px] md:h-[400px] object-cover rounded-lg mb-6"
/>
)}
{/* Content */}
<div>
{blog.content?.blocks?.map((block, index) => renderBlock(block, index))}
</div>
2026-04-14 16:04:44 +05:30
</div>
);
};
export default BlogDetail;