526 lines
27 KiB
JavaScript
526 lines
27 KiB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
|
|
import { useParams, useNavigate, Link } from 'react-router-dom';
|
|
import { useDispatch } from 'react-redux';
|
|
import { message } from 'antd';
|
|
import Lenis from "@studio-freight/lenis";
|
|
import {
|
|
RiArrowLeftLine, RiCalendarLine, RiTimeLine, RiUserLine, RiHomeLine, RiArticleLine,
|
|
RiShareLine,
|
|
RiCloseLine,
|
|
} from 'react-icons/ri';
|
|
import { RxHamburgerMenu } from 'react-icons/rx';
|
|
import { FaFacebook, FaInstagram, FaWhatsapp, FaCopy, FaLinkedin, FaTelegram } from "react-icons/fa";
|
|
import { FaXTwitter } from "react-icons/fa6";
|
|
import { getPublishedBlogs, getPublishedBlogsBySlug } from '../../features/Blog/Blog.js';
|
|
import Loader from '../../PozoApp/Components/Loader/PozoLoader';
|
|
import PozoAppLogo from '../../Images/PozoAppLogo.svg';
|
|
import "./BlogDetail.scss"
|
|
import BlogExtras from "./BlogExtras.jsx";
|
|
import SEO from '../../Components/SEO/SEO.jsx';
|
|
|
|
const mainDirectory = import.meta.env.ENV_MAIN_BASE_URL
|
|
const subDirectory = import.meta.env.ENV_BASE_URL
|
|
|
|
const BlogDetail = () => {
|
|
const { slug } = useParams();
|
|
const navigate = useNavigate();
|
|
const dispatch = useDispatch();
|
|
const [blog, setBlog] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
const [shareOpen, setShareOpen] = useState(false);
|
|
const shareRef = useRef(null);
|
|
console.log(blog, "blogblogblogblogblog")
|
|
useEffect(() => {
|
|
const handleClickOutside = (event) => {
|
|
if (shareRef.current && !shareRef.current.contains(event.target)) {
|
|
setShareOpen(false);
|
|
}
|
|
};
|
|
|
|
const handleEscKey = (event) => {
|
|
if (event.key === 'Escape') {
|
|
setShareOpen(false);
|
|
}
|
|
};
|
|
|
|
if (shareOpen) {
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
document.addEventListener('keydown', handleEscKey);
|
|
}
|
|
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
document.removeEventListener('keydown', handleEscKey);
|
|
};
|
|
}, [shareOpen]);
|
|
|
|
useEffect(() => {
|
|
fetchBlogDetail();
|
|
}, [slug]);
|
|
|
|
useEffect(() => {
|
|
const lenis = new Lenis({
|
|
duration: 1,
|
|
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
|
|
smoothWheel: true,
|
|
smoothTouch: true,
|
|
});
|
|
|
|
function raf(time) {
|
|
lenis.raf(time);
|
|
requestAnimationFrame(raf);
|
|
}
|
|
requestAnimationFrame(raf);
|
|
return () => lenis.destroy();
|
|
}, []);
|
|
|
|
const fetchBlogDetail = async () => {
|
|
try {
|
|
const response = await dispatch(getPublishedBlogsBySlug({ slug })).unwrap();
|
|
|
|
if (response?.data?.statusCode === 1) {
|
|
const blogs = response.data.data?.[0];
|
|
setBlog(blogs);
|
|
} else {
|
|
message.error('Failed to load blog posts');
|
|
}
|
|
setLoading(false);
|
|
} catch (error) {
|
|
console.error('Error fetching blog:', error);
|
|
message.error('Failed to load blog post');
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleBack = () => {
|
|
navigate('/blog');
|
|
};
|
|
|
|
if (loading) {
|
|
return <Loader />;
|
|
}
|
|
|
|
if (!blog) {
|
|
return (
|
|
<div className='BlogDetailMaster'>
|
|
<div className='blog-detail-error'>Blog post not found</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const handleBackHome = () => {
|
|
navigate(`${subDirectory}`);
|
|
}
|
|
|
|
const handleBackBlog = () => {
|
|
navigate(`${subDirectory}blog`);
|
|
}
|
|
|
|
const handlePrivacyPolicy = () => {
|
|
navigate(`${subDirectory}privacy-policy`);
|
|
}
|
|
|
|
const handleNavigateCookie = () => {
|
|
navigate(`${subDirectory}cookie-policy`);
|
|
}
|
|
return (
|
|
<>
|
|
<SEO
|
|
title={blog.BlogTitle}
|
|
description={blog.BlogSubtitle || "Read the latest blog on PozoApp"}
|
|
image={blog.HeaderImage}
|
|
url={`${mainDirectory}/home/blog/${slug}`}
|
|
type="article"
|
|
publishedTime={blog.CreatedDate}
|
|
modifiedTime={blog.UpdatedDate || blog.CreatedDate}
|
|
keywords={blog.Keywords?.join(', ') || "PozoApp, Blog, Technology, Articles"}
|
|
/>
|
|
{drawerOpen && <div className='drawer-overlay' onClick={() => setDrawerOpen(false)}></div>}
|
|
<div className={`drawer ${drawerOpen ? 'open' : ''}`}>
|
|
<div className='drawer-content'>
|
|
{/* <Link to='/' onClick={() => setDrawerOpen(false)}> */}
|
|
<div onClick={handleBackHome}>
|
|
<RiHomeLine /> Home
|
|
</div>
|
|
{/* </Link> */}
|
|
{/* <Link to='/blog' onClick={() => setDrawerOpen(false)}> */}
|
|
<div onClick={handleBackBlog}>
|
|
<RiArticleLine /> Blog
|
|
</div>
|
|
{/* </Link> */}
|
|
</div>
|
|
</div>
|
|
<div className='BlogDetailMaster'>
|
|
<header className='blog-detail-header'>
|
|
<nav className='navbar'>
|
|
<div className='navbar-left'>
|
|
<div className='hamburger-menu' onClick={() => setDrawerOpen(!drawerOpen)}>
|
|
<RxHamburgerMenu size={20} />
|
|
</div>
|
|
<div className='Blognavbar-logo' onClick={handleBackHome}>
|
|
{/* <Link to='/'> */}
|
|
<img src={PozoAppLogo} alt='Logo' className='logo-img' />
|
|
{/* </Link> */}
|
|
<div className='blogdetailsNavbarText'>
|
|
| Blog
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
<div className="blog-detail-contentMain">
|
|
<article className='blog-detail-content'>
|
|
<header className='blog-header'>
|
|
<h1 className='blog-title'>{blog.BlogTitle}</h1>
|
|
<p className='blog-subtitle'>{blog.BlogSubtitle}</p>
|
|
|
|
<div className='blog-meta'>
|
|
<div className='meta-item'>
|
|
<RiUserLine />{blog.Author || "Admin"}
|
|
</div>
|
|
{blog.CreatedDate && (
|
|
<div className='meta-item'>
|
|
<RiCalendarLine /> {(() => {
|
|
if (!blog?.CreatedDate) return '';
|
|
const dateStr = blog.CreatedDate.split('T')[0]; // Get date part
|
|
const [year, month, day] = dateStr.split('-');
|
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
const monthName = months[parseInt(month) - 1];
|
|
return `${parseInt(day)} ${monthName} ${year}`;
|
|
})()}
|
|
</div>
|
|
)}
|
|
|
|
{/* <div className={`status ${blog.IsPublished === 'Y' ? 'published' : 'draft'}`}>
|
|
{blog.IsPublished === 'Y' ? 'Published' : 'Draft'}
|
|
</div> */}
|
|
|
|
</div>
|
|
</header>
|
|
|
|
{blog.HeaderImage && (
|
|
<div >
|
|
<img className='blog-poster' src={blog.HeaderImage} alt={blog.BlogTitle} />
|
|
</div>
|
|
)}
|
|
|
|
<div className='blog-body'>
|
|
{blog.BlogContent && blog.BlogContent.map((block, index) => {
|
|
switch (block.type) {
|
|
case 'header':
|
|
case "oldHeader":
|
|
const HeaderTag = `h${block.data.level || 2}`;
|
|
return (
|
|
<HeaderTag
|
|
key={index}
|
|
className={`blog-header-block level-${block.data.level}`}
|
|
dangerouslySetInnerHTML={{ __html: block.data.text }}
|
|
/>
|
|
);
|
|
|
|
case 'paragraph':
|
|
return (
|
|
<p
|
|
key={index}
|
|
className="blog-paragraph"
|
|
dangerouslySetInnerHTML={{ __html: block.data.text }}
|
|
/>
|
|
);
|
|
|
|
case 'list':
|
|
if (block.data.style === 'checklist') {
|
|
return (
|
|
<ul key={index} className='blog-list checklist'>
|
|
{block.data.items.map((item, i) => (
|
|
<li key={i} className='checklist-item'>
|
|
<input type="checkbox" checked={item.meta?.checked} readOnly />
|
|
<span>{item.content}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
} else if (block.data.style === 'ordered') {
|
|
return (
|
|
<ol key={index} className='blog-list ordered'>
|
|
{block.data.items.map((item, i) => (
|
|
<li key={i}>{item.content}</li>
|
|
))}
|
|
</ol>
|
|
);
|
|
} else {
|
|
return (
|
|
<ul key={index} className='blog-list unordered'>
|
|
{block.data.items.map((item, i) => (
|
|
<li key={i}>{item.content}</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
}
|
|
|
|
case "image":
|
|
return (
|
|
<div key={index} className="blog-image">
|
|
<img
|
|
src={block.data.file?.url}
|
|
alt={block.data.caption || "Blog image"}
|
|
/>
|
|
{block.data.caption && (
|
|
<p className="caption">{block.data.caption}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
case "separator":
|
|
return (
|
|
<hr
|
|
key={index}
|
|
className={`blog-separator ${block.data.style || "line"
|
|
}`}
|
|
/>
|
|
);
|
|
|
|
case "video":
|
|
return (
|
|
<div key={index} className="blog-video">
|
|
{block.data.file?.url && (
|
|
<video
|
|
src={block.data.file.url}
|
|
controls
|
|
className="video-player"
|
|
/>
|
|
)}
|
|
{block.data.caption && (
|
|
<p className="caption">{block.data.caption}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
case "link":
|
|
return (
|
|
<div key={index} className="blog-link">
|
|
<a href={block.data.url} target={block.data.target || "_self"}>
|
|
{block.data.text}
|
|
</a>
|
|
</div>
|
|
);
|
|
|
|
case "quote":
|
|
return (
|
|
<blockquote key={index} className="blog-quote">
|
|
<p>{block.data.text}</p>
|
|
{block.data.caption && (
|
|
<cite>{block.data.caption}</cite>
|
|
)}
|
|
</blockquote>
|
|
);
|
|
|
|
case "code":
|
|
return (
|
|
<pre key={index} className="blog-code">
|
|
<code>{block.data.code}</code>
|
|
</pre>
|
|
);
|
|
|
|
case "cta":
|
|
return (
|
|
<div key={index} className="blog-cta">
|
|
<h3>{block.data.heading}</h3>
|
|
<p>{block.data.subheading}</p>
|
|
<a href={block.data.buttonUrl} className={`cta-button ${block.data.style}`}>
|
|
{block.data.buttonText}
|
|
</a>
|
|
</div>
|
|
);
|
|
|
|
case "email":
|
|
return (
|
|
<div key={index} className="blog-email">
|
|
<h4>Subject: {block.data.subject}</h4>
|
|
<div dangerouslySetInnerHTML={{ __html: block.data.bodyHtml }} />
|
|
</div>
|
|
);
|
|
|
|
case "bookmark":
|
|
return (
|
|
<div key={index} className="blog-bookmark">
|
|
<a href={block.data.url} target="_blank" rel="noopener noreferrer" style={{ display: 'flex', alignItems: 'center', gap: '16px', textDecoration: 'none', border: '1px solid #e5e7eb', borderRadius: '8px', padding: '16px' }}>
|
|
<div className="bookmark-content" style={{ flex: 1 }}>
|
|
<h4 style={{ margin: '0 0 8px 0', color: '#1f2937' }}>{block.data.title}</h4>
|
|
<p style={{ margin: '0 0 8px 0', color: '#6b7280', fontSize: '14px' }}>{block.data.description}</p>
|
|
<span className="bookmark-url" style={{ color: '#3b82f6', fontSize: '12px' }}>{block.data.url}</span>
|
|
</div>
|
|
{block.data.image && (
|
|
<img src={block.data.image} alt={block.data.title} style={{ width: '120px', height: '90px', objectFit: 'cover', borderRadius: '6px', flexShrink: 0 }} />
|
|
)}
|
|
</a>
|
|
</div>
|
|
);
|
|
case "callout":
|
|
return (
|
|
<div key={index} className="blog-callout">
|
|
<span className="callout-emoji">{block.data.emoji || "💡"}</span>
|
|
<p>{block.data.text}</p>
|
|
</div>
|
|
);
|
|
|
|
case "index":
|
|
const IndexListTag = block.data.style === "numbered" ? "ol" : "ul";
|
|
return (
|
|
<div key={index} className="blog-index">
|
|
<h4>Table of Contents</h4>
|
|
<IndexListTag>
|
|
{(block.data.items || []).map((item, i) => (
|
|
<li key={i}>
|
|
<a href={`#${item.anchor}`}>{item.title}</a>
|
|
</li>
|
|
))}
|
|
</IndexListTag>
|
|
</div>
|
|
);
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
})}
|
|
<div className="shareBlog">
|
|
<button
|
|
className="share-btn"
|
|
onClick={() => setShareOpen(!shareOpen)}
|
|
>
|
|
<RiShareLine /> Share
|
|
</button>
|
|
|
|
{shareOpen && (
|
|
<div className="share-options" ref={shareRef}>
|
|
<div className="share-header">
|
|
<span>Share to</span>
|
|
<button onClick={() => setShareOpen(false)}>
|
|
<RiCloseLine size={20} />
|
|
</button>
|
|
</div>
|
|
<div className="share-buttons">
|
|
<button
|
|
onClick={() => {
|
|
const url = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
|
|
window.location.href
|
|
)}`;
|
|
window.open(url, "_blank");
|
|
}}
|
|
className="share-facebook"
|
|
>
|
|
<FaFacebook />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => {
|
|
const url = `https://wa.me/?text=${encodeURIComponent(
|
|
blog.BlogTitle + " " + window.location.href
|
|
)}`;
|
|
window.open(url, "_blank");
|
|
}}
|
|
className="share-whatsapp"
|
|
>
|
|
<FaWhatsapp />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => {
|
|
const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(
|
|
blog.BlogTitle
|
|
)}&url=${encodeURIComponent(window.location.href)}`;
|
|
window.open(url, "_blank");
|
|
}}
|
|
className="share-twitter"
|
|
>
|
|
<FaXTwitter />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => {
|
|
const url = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(
|
|
window.location.href
|
|
)}`;
|
|
window.open(url, "_blank");
|
|
}}
|
|
className="share-linkedin"
|
|
>
|
|
<FaLinkedin />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => {
|
|
const url = `https://t.me/share/url?url=${encodeURIComponent(
|
|
window.location.href
|
|
)}&text=${encodeURIComponent(blog.BlogTitle)}`;
|
|
window.open(url, "_blank");
|
|
}}
|
|
className="share-telegram"
|
|
>
|
|
<FaTelegram />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => {
|
|
window.open("https://www.instagram.com/", "_blank");
|
|
navigator.clipboard.writeText(window.location.href);
|
|
message.success(
|
|
"Instagram opened! Link copied to paste in your story or bio."
|
|
);
|
|
}}
|
|
className="share-instagram"
|
|
>
|
|
<FaInstagram />
|
|
</button>
|
|
|
|
<button
|
|
onClick={async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(
|
|
window.location.href
|
|
);
|
|
message.success("Link copied to clipboard!");
|
|
} catch (err) {
|
|
// Fallback for older browsers
|
|
const textArea = document.createElement("textarea");
|
|
textArea.value = window.location.href;
|
|
document.body.appendChild(textArea);
|
|
textArea.select();
|
|
document.execCommand("copy");
|
|
document.body.removeChild(textArea);
|
|
message.success("Link copied to clipboard!");
|
|
}
|
|
}}
|
|
className="share-copy"
|
|
>
|
|
<FaCopy /> Copy Link
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
<BlogExtras allowComments={blog?.AllowComments} blogId={blog?.BlogId} />
|
|
</div>
|
|
</div>
|
|
<div className="liveFooterRights">
|
|
<div>
|
|
© 2025 PozoApp
|
|
<div className='linktoPrivayCookie' onClick={handlePrivacyPolicy}>
|
|
Privacy Policy
|
|
</div>
|
|
<div className='linktoPrivayCookie' onClick={handleNavigateCookie}>
|
|
Cookie Policy
|
|
</div>
|
|
</div>
|
|
<p>
|
|
For queries contact us: Manager, No 51 Step Colony, Dharga,Hosur,
|
|
Krishnagiri, Tamilnadu-635126, India
|
|
</p>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default BlogDetail;
|