feat: make blog data dynamic
This commit is contained in:
@@ -1,39 +1,55 @@
|
||||
"use client";
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Card, CardHeader, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import axios from "axios";
|
||||
import { backendUrl } from "@/utils/axios";
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
const BlogPost = ({ title, author, date, excerpt, imageUrl }) => (
|
||||
<div className="mb-6 flex sm:flex-row flex-col">
|
||||
<div className=" h-full bg-purple-100 sm:w-1/2">
|
||||
<Link href={`/blogs/blog/${title}`}>
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={title || "Image"}
|
||||
className="object-cover rounded-md"
|
||||
layout="responsive"
|
||||
width={500}
|
||||
height={300}
|
||||
const BlogPost = ({ title, author, date, excerpt, imageUrl, slug }) => {
|
||||
const sanitizedExcerpt = DOMPurify.sanitize(excerpt);
|
||||
|
||||
const formattedImageUrl = imageUrl !== "NA"
|
||||
? (imageUrl.startsWith('http') ? imageUrl : `${backendUrl}${imageUrl}`)
|
||||
: "/api/placeholder/300/200";
|
||||
|
||||
return (
|
||||
<div className="mb-6 flex sm:flex-row flex-col">
|
||||
<div className=" h-full bg-purple-100 sm:w-1/2">
|
||||
<Link href={`/blogs/blog/${slug}`}>
|
||||
<Image
|
||||
src={formattedImageUrl}
|
||||
alt={title || "Image"}
|
||||
className="object-cover rounded-md"
|
||||
layout="responsive"
|
||||
width={500}
|
||||
height={300}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="sm:w-2/3 p-4">
|
||||
<Link href={`/blogs/blog/${slug}`}>
|
||||
<h3 className="text-xl sm:text-2xl font-bold">{title}</h3>
|
||||
</Link>
|
||||
<p className="text-sm sm:text-lg text-gray-500 mb-2">
|
||||
{author} | {date}
|
||||
</p>
|
||||
<div
|
||||
className="text-sm blog-content"
|
||||
dangerouslySetInnerHTML={{ __html: sanitizedExcerpt }}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:w-2/3 p-4">
|
||||
<Link href={`/blogs/blog/${title}`}>
|
||||
<h3 className="text-xl sm:text-2xl font-bold">{title}</h3>
|
||||
</Link>
|
||||
<p className="text-sm sm:text-lg text-gray-500 mb-2">
|
||||
{author} | {date}
|
||||
</p>
|
||||
<p className="text-sm">{excerpt}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
const PopularArticle = ({ title, author, date }) => (
|
||||
const PopularArticle = ({ title, author, date, slug }) => (
|
||||
<div className="mb-4">
|
||||
<h4 className="font-semibold">{title}</h4>
|
||||
<Link href={`/blogs/blog/${slug}`}>
|
||||
<h4 className="font-semibold">{title}</h4>
|
||||
</Link>
|
||||
<p className="text-sm text-gray-500">
|
||||
{author} | {date}
|
||||
</p>
|
||||
@@ -42,63 +58,77 @@ const PopularArticle = ({ title, author, date }) => (
|
||||
|
||||
const BlogHome = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [blogData, setBlogData] = useState({
|
||||
recentBlogPosts: [],
|
||||
popularArticles: []
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const postsPerPage = 3;
|
||||
|
||||
const recentBlogPosts = [
|
||||
{
|
||||
title: "Benefits of Wearing Rudraksha Mala",
|
||||
author: "Gupta Rudraksha",
|
||||
date: "29 August, 2024",
|
||||
excerpt:
|
||||
"Rudraksha are sacred beads of great significance in Hinduism and various spiritual practices. The term Rudraksha combines two Sanskrit words: 'Rudra,' another name for Lord Sh...",
|
||||
imageUrl: "/blogs/significance-of-dhanteras.webp",
|
||||
},
|
||||
{
|
||||
title: "Shravan Maas for Spiritual Growth and Divine Connection",
|
||||
author: "Gupta Rudraksha",
|
||||
date: "04 September, 2024",
|
||||
excerpt:
|
||||
"Shrawan Mass, a sacred month in the Hindu calendar, holds deep spiritual significance for millions of devotees. But what exactly is Shravan Maas, and why is it so important? Let...",
|
||||
imageUrl: "/blogs/navaratri-siginificance.webp",
|
||||
},
|
||||
{
|
||||
title: "The Complete Guide to Rudraksha Energization",
|
||||
author: "Gupta Rudraksha",
|
||||
date: "28 August, 2024",
|
||||
excerpt:
|
||||
"For centuries, Rudraksha beads have been valued not only for their aesthetic beauty, but also for their powerful spiritual and healing properties. Whether you're a seasoned prac...",
|
||||
imageUrl: "/blogs/rudraksha-pran-pratishtha-pooja.webp",
|
||||
},
|
||||
{
|
||||
title: "Strengthening Planetary Forces with Rudraksha",
|
||||
author: "Gupta Rudraksha",
|
||||
date: "26 April, 2024",
|
||||
excerpt:
|
||||
"In the vast universe, planets hold immense power over our lives, influencing everything from our moods to our destinies. However, Rudraksha beads, ancient treasures from the ear...",
|
||||
imageUrl: "/api/placeholder/300/200",
|
||||
},
|
||||
];
|
||||
useEffect(() => {
|
||||
const fetchBlogData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`${backendUrl}/blogs/`);
|
||||
setBlogData(response.data);
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
console.error("Error fetching blog data:", err);
|
||||
setError("Failed to load blog data. Please try again later.");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const popularArticles = [
|
||||
{
|
||||
title: "Dhanteras Significance and How Rudraksha Brings Prosperity",
|
||||
author: "Gupta Rudraksha",
|
||||
date: "30 September, 2024",
|
||||
},
|
||||
{
|
||||
title:
|
||||
"Certified Rudraksha: Nepal's 1st ISO 9001:2015 Certified Organization",
|
||||
author: "Gupta Admin",
|
||||
date: "30 September, 2024",
|
||||
},
|
||||
];
|
||||
fetchBlogData();
|
||||
}, []);
|
||||
|
||||
// Get featured blog (first blog from recent posts)
|
||||
const featuredBlog = blogData.recentBlogPosts && blogData.recentBlogPosts.length > 0
|
||||
? blogData.recentBlogPosts[0]
|
||||
: null;
|
||||
|
||||
// Get remaining blogs for pagination
|
||||
const remainingBlogs = blogData.recentBlogPosts && blogData.recentBlogPosts.length > 0
|
||||
? blogData.recentBlogPosts.slice(1)
|
||||
: [];
|
||||
|
||||
const indexOfLastPost = currentPage * postsPerPage;
|
||||
const indexOfFirstPost = indexOfLastPost - postsPerPage;
|
||||
const currentPosts = recentBlogPosts.slice(indexOfFirstPost, indexOfLastPost);
|
||||
const currentPosts = remainingBlogs.slice(indexOfFirstPost, indexOfLastPost);
|
||||
|
||||
const paginate = (pageNumber) => setCurrentPage(pageNumber);
|
||||
|
||||
const featuredImageUrl = featuredBlog && featuredBlog.imageUrl !== "NA"
|
||||
? (featuredBlog.imageUrl.startsWith('http') ? featuredBlog.imageUrl : `${backendUrl}${featuredBlog.imageUrl}`)
|
||||
: "/api/placeholder/300/200";
|
||||
|
||||
if (loading) {
|
||||
return <div className="container mx-auto px-4 py-8">Loading blogs...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div className="container mx-auto px-4 py-8 text-red-500">{error}</div>;
|
||||
}
|
||||
|
||||
// Check if there are no blogs at all
|
||||
if (!blogData.recentBlogPosts || blogData.recentBlogPosts.length === 0) {
|
||||
return (
|
||||
<div className="container max-w-8xl mx-auto sm:px-20 px-3 py-20 text-center">
|
||||
<h1 className="sm:text-5xl pt-3 text-3xl font-bold mb-8">
|
||||
Insights from Gupta Rudraksha
|
||||
</h1>
|
||||
<div className="bg-gray-100 p-10 rounded-lg shadow-md">
|
||||
<h2 className="text-2xl font-semibold text-gray-700 mb-4">No Blogs Found</h2>
|
||||
<p className="text-gray-600">
|
||||
We're currently working on creating insightful content for you.
|
||||
Please check back later for updates on Rudraksha and spiritual practices.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container max-w-8xl mx-auto sm:px-20 px-3 ">
|
||||
<h1 className="sm:text-5xl pt-3 text-3xl font-bold mb-8">
|
||||
@@ -108,73 +138,88 @@ const BlogHome = () => {
|
||||
Explore our latest articles on the spiritual, cultural, and healing
|
||||
aspects
|
||||
</p>
|
||||
{/* top container for one blog card */}
|
||||
<dir className="min-h-[30vh] flex sm:flex-row flex-col-reverse">
|
||||
<div className="mb-6 flex sm:flex-row flex-col">
|
||||
<div className="h-full bg-purple-100">
|
||||
<Link href={`/blogs/blog/`}>
|
||||
<Image
|
||||
src="/blogs/significance-of-dhanteras.webp"
|
||||
alt="images blog"
|
||||
layout="intrinsic"
|
||||
width={600}
|
||||
height={400}
|
||||
className="object-cover rounded-md"
|
||||
|
||||
{/* Featured blog section */}
|
||||
{featuredBlog && (
|
||||
<div className="min-h-[30vh] flex sm:flex-row flex-col-reverse">
|
||||
<div className="mb-6 flex sm:flex-row flex-col">
|
||||
<div className="h-full bg-purple-100">
|
||||
<Link href={`/blogs/blog/${featuredBlog.slug}`}>
|
||||
<Image
|
||||
src={featuredImageUrl}
|
||||
alt={featuredBlog.title}
|
||||
layout="intrinsic"
|
||||
width={600}
|
||||
height={400}
|
||||
className="object-cover rounded-md"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="sm:w-2/3 p-4">
|
||||
<Link href={`/blogs/blog/${featuredBlog.slug}`}>
|
||||
<h3 className="text-xl sm:text-2xl font-bold">
|
||||
{featuredBlog.title}
|
||||
</h3>
|
||||
</Link>
|
||||
<p className="text-sm sm:text-lg text-gray-500 mb-2">
|
||||
{featuredBlog.author} | {featuredBlog.date}
|
||||
</p>
|
||||
<div
|
||||
className="text-sm py-4 line-clamp-2 blog-content"
|
||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(featuredBlog.excerpt) }}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:w-2/3 p-4">
|
||||
<Link href={`/blogs/blog/`}>
|
||||
<h3 className="text-xl sm:text-2xl font-bold">
|
||||
Dhanteras Significance and How Rudraksha Brings Prosperity and
|
||||
Protection
|
||||
</h3>
|
||||
</Link>
|
||||
<p className="text-sm sm:text-lg text-gray-500 mb-2">
|
||||
Gupta Rudraksha | 30 September, 2024
|
||||
</p>
|
||||
<p className="text-sm py-4 line-clamp-2">
|
||||
Dhanteras, the first day of Diwali festival, marks a unique
|
||||
celebration of wealth and prosperity in Hindu tradition. As the
|
||||
name suggests - 'Dhan' meaning wealth and
|
||||
'Teras' ref
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full lg:w-1/3 px-4">
|
||||
<h2 className="text-2xl font-bold mb-4">Popular Articles</h2>
|
||||
{popularArticles.map((article, index) => (
|
||||
<PopularArticle key={index} {...article} />
|
||||
))}
|
||||
</div>
|
||||
</dir>
|
||||
|
||||
<div className="flex flex-wrap -mx-4">
|
||||
<div className="w-full lg:w-2/3 px-4">
|
||||
<h2 className="sm:text-4xl text-2xl font-bold mb-4">Recent Blogs</h2>
|
||||
{currentPosts.map((post, index) => (
|
||||
<BlogPost key={index} {...post} />
|
||||
))}
|
||||
<div className="flex justify-center space-x-2 mb-8 items-center pt-6">
|
||||
{[...Array(Math.ceil(recentBlogPosts.length / postsPerPage))].map(
|
||||
(_, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
onClick={() => paginate(index + 1)}
|
||||
variant={currentPage === index + 1 ? "default" : "outline"}
|
||||
>
|
||||
{index + 1}
|
||||
</Button>
|
||||
)
|
||||
<div className="w-full lg:w-1/3 px-4">
|
||||
<h2 className="text-2xl font-bold mb-4">Popular Articles</h2>
|
||||
{blogData.popularArticles && blogData.popularArticles.length > 0 ? (
|
||||
blogData.popularArticles.map((article, index) => (
|
||||
<PopularArticle key={index} {...article} />
|
||||
))
|
||||
) : (
|
||||
<p className="text-gray-500">No popular articles available</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap -mx-4">
|
||||
<div className="w-full lg:w-2/3 px-4">
|
||||
<h2 className="sm:text-4xl text-2xl font-bold mb-4">Recent Blogs</h2>
|
||||
{currentPosts.length > 0 ? (
|
||||
currentPosts.map((post, index) => (
|
||||
<BlogPost key={post.id || index} {...post} />
|
||||
))
|
||||
) : (
|
||||
<p className="text-gray-500 py-4">No recent blogs available</p>
|
||||
)}
|
||||
|
||||
{remainingBlogs.length > postsPerPage && (
|
||||
<div className="flex justify-center space-x-2 mb-8 items-center pt-6">
|
||||
{[...Array(Math.ceil(remainingBlogs.length / postsPerPage))].map(
|
||||
(_, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
onClick={() => paginate(index + 1)}
|
||||
variant={currentPage === index + 1 ? "default" : "outline"}
|
||||
>
|
||||
{index + 1}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-1/3 px-4">
|
||||
<h2 className="text-2xl font-bold mb-4">Popular Articles</h2>
|
||||
{popularArticles.map((article, index) => (
|
||||
<PopularArticle key={index} {...article} />
|
||||
))}
|
||||
{blogData.popularArticles && blogData.popularArticles.length > 0 ? (
|
||||
blogData.popularArticles.map((article, index) => (
|
||||
<PopularArticle key={index} {...article} />
|
||||
))
|
||||
) : (
|
||||
<p className="text-gray-500">No popular articles available</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,43 +2,120 @@
|
||||
import Image from "next/image";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "./blog.css";
|
||||
import axios from "axios";
|
||||
import { backendUrl } from "@/utils/axios";
|
||||
import DOMPurify from 'dompurify';
|
||||
import { useParams } from "next/navigation";
|
||||
|
||||
|
||||
|
||||
const SingleBlog = () => {
|
||||
const [blogResData, setResData] = useState(null);
|
||||
const [blog, setBlog] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Debug log to check if slug is received
|
||||
const params = useParams();
|
||||
|
||||
console.log("BlogPage params:", params);
|
||||
console.log("SingleBlog received slug:", params?.["blog-id"]);
|
||||
const slug = params?.["blog-id"]
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const fetchBlogData = async () => {
|
||||
if (!slug) {
|
||||
console.error("No slug provided");
|
||||
setError("Blog not found. Missing identifier.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/blog");
|
||||
const blog = await response.json();
|
||||
setResData(blog.blogdata);
|
||||
} catch (error) {
|
||||
console.error("Error fetching blog data:", error);
|
||||
console.log(`Fetching blog with slug: ${slug}`);
|
||||
setLoading(true);
|
||||
|
||||
// Log the full URL being called for debugging
|
||||
const url = `${backendUrl}/blogs/${slug}/`;
|
||||
console.log(`Making API call to: ${url}`);
|
||||
|
||||
const response = await axios.get(url);
|
||||
console.log("API response:", response.data);
|
||||
|
||||
if (response.data) {
|
||||
setBlog(response.data);
|
||||
} else {
|
||||
setError("No blog data received from server");
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
console.error("Error fetching blog data:", err);
|
||||
setError(`Failed to load blog content: ${err.message || "Unknown error"}`);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
fetchBlogData();
|
||||
}, [slug]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-5 text-center">
|
||||
<p className="text-gray-500">Loading blog content for slug: {slug}...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-5 text-center">
|
||||
<p className="text-red-500">Error: {error}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!blog) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-5 text-center">
|
||||
<p className="text-gray-500">Blog not found for slug: {slug}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Format the image URL
|
||||
const imageUrl = blog.imageUrl !== "NA"
|
||||
? (blog.imageUrl.startsWith('http') ? blog.imageUrl : `${backendUrl}${blog.imageUrl}`)
|
||||
: "/api/placeholder/800/500";
|
||||
|
||||
// Sanitize the blog description
|
||||
const sanitizedDescription = DOMPurify.sanitize(blog.description);
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-5 space-y-6 bg-white shadow-lg rounded-lg">
|
||||
{/* Blog Title */}
|
||||
<h1 className="text-3xl font-bold text-center mb-6">{blog.title}</h1>
|
||||
|
||||
{/* Blog Meta Information */}
|
||||
<div className="text-center text-gray-600 mb-6">
|
||||
<p>{blog.author} | {blog.date}</p>
|
||||
</div>
|
||||
|
||||
{/* Blog Image */}
|
||||
<div className="w-full flex justify-center">
|
||||
<Image
|
||||
src={"/blogs/significance-of-dhanteras.webp"}
|
||||
src={imageUrl}
|
||||
height={500}
|
||||
width={800}
|
||||
alt="Blog Cover Image"
|
||||
alt={blog.title || "Blog Cover Image"}
|
||||
className="rounded-lg object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Blog Content */}
|
||||
<div className="prose lg:prose-xl mx-auto text-gray-800 leading-relaxed">
|
||||
{blogResData ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: blogResData }} />
|
||||
) : (
|
||||
<p className="text-center text-gray-500">Loading blog content...</p>
|
||||
)}
|
||||
<div
|
||||
className="blog-content"
|
||||
dangerouslySetInnerHTML={{ __html: sanitizedDescription }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user