Files
cmc_frontend/src/components/publications/BlogDetail.tsx
2025-10-29 08:52:43 +05:30

421 lines
15 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import { ChevronRight, Clock, Calendar, Share2, ArrowLeft, Facebook, Twitter, Linkedin } from 'lucide-react';
import { blogService, Blog } from '../../services/blogService'; // Adjust path as needed
const BlogDetail: React.FC = () => {
const params = useParams();
const router = useRouter();
const blogId = params.id as string;
const [blogData, setBlogData] = useState<Blog | null>(null);
const [relatedPosts, setRelatedPosts] = useState<Blog[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [mounted, setMounted] = useState(false);
useEffect(() => {
const fetchBlogData = async () => {
if (!blogId) return;
setLoading(true);
setError(null);
try {
// Convert string ID to number for API call
const numericId = parseInt(blogId, 10);
if (isNaN(numericId)) {
throw new Error('Invalid blog ID');
}
console.log('Fetching blog with ID:', numericId);
// Fetch the specific blog and related posts
const [blog, allBlogs] = await Promise.all([
blogService.getBlogById(numericId),
blogService.getPostedBlogs()
]);
if (!blog) {
setError('Blog not found');
return;
}
setBlogData(blog);
// Get related posts (same tags, exclude current blog)
const related = allBlogs
.filter(b => b.id !== blog.id && b.tags.some(tag => blog.tags.includes(tag)))
.slice(0, 3);
// If not enough related posts with same tags, fill with other recent posts
if (related.length < 3) {
const otherPosts = allBlogs
.filter(b => b.id !== blog.id && !related.some(r => r.id === b.id))
.slice(0, 3 - related.length);
related.push(...otherPosts);
}
setRelatedPosts(related);
} catch (err) {
console.error('Error fetching blog:', err);
setError('Failed to load blog. Please try again later.');
} finally {
setLoading(false);
}
};
if (mounted) {
fetchBlogData();
}
}, [blogId, mounted]);
useEffect(() => {
setMounted(true);
}, []);
const handleGoBack = () => {
router.back();
};
const handleShare = async (platform?: string) => {
if (!blogData || typeof window === 'undefined') return;
const url = window.location.href;
const title = blogData.title;
try {
if (platform === 'facebook') {
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`, '_blank');
} else if (platform === 'twitter') {
window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`, '_blank');
} else if (platform === 'linkedin') {
window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`, '_blank');
} else {
// Generic share or copy link
if (navigator.share) {
await navigator.share({
title: title,
url: url,
});
} else {
await navigator.clipboard.writeText(url);
alert('Blog link copied to clipboard!');
}
}
} catch (error) {
console.error('Share failed:', error);
}
};
const handleImageError = (event: React.SyntheticEvent<HTMLImageElement>) => {
const target = event.target as HTMLImageElement;
target.src = '/images/default-blog-image.jpg';
};
const getAuthorName = (blog: Blog) => {
if (blog.professors && blog.professors.length > 0) {
return blog.professors.map(prof => prof.firstName || prof.name).join(', ');
}
return 'Medical Team';
};
const getAuthorBio = (blog: Blog) => {
if (blog.professors && blog.professors.length > 0) {
return `Medical professional${blog.professors.length > 1 ? 's' : ''} specializing in trauma care and mental health support.`;
}
return 'Our medical team consists of experienced professionals dedicated to trauma care and mental health support.';
};
if (!mounted) {
return null;
}
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-900 mx-auto mb-4"></div>
<p className="text-gray-600">Loading blog...</p>
</div>
</div>
);
}
if (error || !blogData) {
return (
<div className="min-h-screen bg-white">
{/* Breadcrumb Section */}
<section className="py-4" style={{ backgroundColor: '#f4f4f4' }}>
<div className="max-w-7xl mx-auto px-4">
<nav className="flex items-center space-x-2 text-sm mb-4">
<Link
href="/"
className="hover:opacity-70 transition-opacity duration-200"
style={{ color: '#012068' }}
>
Home
</Link>
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
<Link
href="/blogs"
className="hover:opacity-70 transition-opacity duration-200"
style={{ color: '#012068' }}
>
Trauma Care Resources
</Link>
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
<span className="font-medium" style={{ color: '#e64838' }}>
Blog Not Found
</span>
</nav>
<button
onClick={handleGoBack}
className="inline-flex items-center space-x-2 text-sm hover:opacity-70 transition-opacity duration-200"
style={{ color: '#012068' }}
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Resources</span>
</button>
</div>
</section>
<div className="py-6">
<div className="max-w-7xl mx-auto px-4">
<div className="bg-white shadow-lg rounded-lg p-4 md:p-8 text-center">
<h1 className="text-xl md:text-2xl font-medium mb-4" style={{ color: '#012068' }}>
{error || 'Blog Not Found'}
</h1>
<button
onClick={handleGoBack}
className="px-6 py-2 text-sm rounded-lg hover:opacity-90 transition-opacity"
style={{ backgroundColor: '#012068', color: '#f4f4f4' }}
>
Go Back
</button>
</div>
</div>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-white">
{/* Breadcrumb Section */}
<section className="py-4" style={{ backgroundColor: '#f4f4f4' }}>
<div className="max-w-7xl mx-auto px-4">
<nav className="flex items-center space-x-2 text-sm mb-4">
<Link
href="/"
className="hover:opacity-70 transition-opacity duration-200"
style={{ color: '#012068' }}
>
Home
</Link>
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
<Link
href="/blogs"
className="hover:opacity-70 transition-opacity duration-200"
style={{ color: '#012068' }}
>
Trauma Care Resources
</Link>
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
<span className="font-medium truncate" style={{ color: '#e64838' }}>
{blogData.title}
</span>
</nav>
{/* Back Button */}
<button
onClick={handleGoBack}
className="inline-flex items-center space-x-2 text-sm hover:opacity-70 transition-opacity duration-200"
style={{ color: '#012068' }}
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Resources</span>
</button>
</div>
</section>
{/* Article Content */}
<article className="max-w-7xl mx-auto px-4 py-8">
{/* Article Header */}
<header className="mb-8">
{/* Tags */}
<div className="flex flex-wrap gap-2 mb-4">
{blogData.tags.map((tag, index) => (
<span
key={index}
className="px-3 py-1 text-sm font-medium rounded"
style={{
backgroundColor: '#f4f4f4',
color: '#e64838'
}}
>
{tag}
</span>
))}
</div>
{/* Title */}
<h1
className="text-3xl md:text-4xl font-bold mb-4 leading-tight"
style={{ color: '#012068' }}
>
{blogData.title}
</h1>
{/* Meta Information */}
<div className="flex flex-wrap items-center gap-4 text-sm" style={{ color: '#666' }}>
<div className="flex items-center space-x-2">
<Calendar className="w-4 h-4" />
<span>{new Date(blogData.publishDate).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}</span>
</div>
<div className="flex items-center space-x-2">
<Clock className="w-4 h-4" />
<span>{blogData.readTime}</span>
</div>
</div>
{/* Author Info */}
<div className="flex items-start space-x-4 mt-6 p-4 rounded-lg" style={{ backgroundColor: '#f4f4f4' }}>
<div className="w-15 h-15 bg-gray-300 rounded-full flex items-center justify-center flex-shrink-0">
<span className="text-2xl font-medium" style={{ color: '#012068' }}>
{getAuthorName(blogData).charAt(0)}
</span>
</div>
<div>
<h3 className="font-medium" style={{ color: '#012068' }}>
{getAuthorName(blogData)}
</h3>
<p className="text-sm mt-1" style={{ color: '#666' }}>
{getAuthorBio(blogData)}
</p>
</div>
</div>
</header>
{/* Share Buttons */}
<div className="flex items-center space-x-4 mb-8 pb-6 border-b border-gray-200">
<span className="text-sm font-medium" style={{ color: '#012068' }}>Share:</span>
<button
onClick={() => handleShare('facebook')}
className="p-2 rounded hover:bg-gray-100 transition-colors duration-200"
>
<Facebook className="w-5 h-5" style={{ color: '#1877f2' }} />
</button>
<button
onClick={() => handleShare('twitter')}
className="p-2 rounded hover:bg-gray-100 transition-colors duration-200"
>
<Twitter className="w-5 h-5" style={{ color: '#1da1f2' }} />
</button>
<button
onClick={() => handleShare('linkedin')}
className="p-2 rounded hover:bg-gray-100 transition-colors duration-200"
>
<Linkedin className="w-5 h-5" style={{ color: '#0077b5' }} />
</button>
<button
onClick={() => handleShare()}
className="p-2 rounded hover:bg-gray-100 transition-colors duration-200"
>
<Share2 className="w-5 h-5" style={{ color: '#666' }} />
</button>
</div>
{/* Article Content */}
<div
className="prose prose-lg max-w-none mb-12"
style={{
'--tw-prose-headings': '#012068',
'--tw-prose-body': '#333',
'--tw-prose-links': '#e64838',
color:'#333'
} as React.CSSProperties}
dangerouslySetInnerHTML={{ __html: blogData.content || blogData.excerpt }}
/>
</article>
{/* Related Posts */}
{relatedPosts.length > 0 && (
<section className="py-12" style={{ backgroundColor: '#f4f4f4' }}>
<div className="max-w-7xl mx-auto px-4">
<h2 className="text-2xl font-bold mb-8" style={{ color: '#012068' }}>
Related Articles
</h2>
<div className="grid gap-6 md:grid-cols-3">
{relatedPosts.map((post) => (
<div
key={post.id}
className="group bg-white rounded-lg overflow-hidden border border-gray-300 hover:shadow-lg transition-all duration-300"
>
<Link href={`/publications-detail/${post.id}`} className="block">
<div className="relative h-40 overflow-hidden">
<Image
src={post.image}
alt={post.title}
fill
className="object-cover transition-transform duration-300 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 33vw"
onError={handleImageError}
/>
</div>
<div className="p-4">
<h3
className="font-medium text-sm line-clamp-2 mb-2 group-hover:opacity-70 transition-opacity duration-300"
style={{ color: '#012068' }}
>
{post.title}
</h3>
<div className="flex items-center space-x-2 text-xs" style={{ color: '#666' }}>
<Clock className="w-3 h-3" />
<span>{post.readTime}</span>
</div>
</div>
</Link>
</div>
))}
</div>
</div>
</section>
)}
{/* CTA Section */}
<section className="py-12">
<div className="max-w-4xl mx-auto px-4 text-center">
<div className="p-8 rounded-lg" style={{ backgroundColor: '#f4f4f4' }}>
<h2 className="text-2xl font-bold mb-4" style={{ color: '#012068' }}>
Need Professional Support?
</h2>
<p className="text-base mb-6 max-w-2xl mx-auto" style={{ color: '#666' }}>
If you or someone you know is struggling with trauma, don&apos;t hesitate to reach out for professional help. Our team is here to support you on your healing journey.
</p>
<Link
href="/contact"
className="inline-block px-6 py-3 text-sm font-medium rounded hover:opacity-90 transition-opacity duration-300"
style={{
backgroundColor: '#012068',
color: '#f4f4f4'
}}
>
Get Support Today
</Link>
</div>
</div>
</section>
</div>
);
};
export default BlogDetail;