421 lines
15 KiB
TypeScript
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'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; |