122 lines
5.1 KiB
JavaScript
122 lines
5.1 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import matter from 'gray-matter';
|
|
|
|
function Blog() {
|
|
const [posts, setPosts] = useState([]);
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [selectedTag, setSelectedTag] = useState('');
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function fetchPosts() {
|
|
try {
|
|
const postFiles = import.meta.glob('../blogs/*.md', { query: '?raw', import: 'default', eager: true });
|
|
const loadedPosts = [];
|
|
for (const path in postFiles) {
|
|
const content = postFiles[path];
|
|
const { data } = matter(content);
|
|
const slug = path.split('/').pop().replace('.md', '');
|
|
loadedPosts.push({ slug, ...data });
|
|
}
|
|
loadedPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
setPosts(loadedPosts);
|
|
} catch (error) {
|
|
console.error("Error fetching blog posts:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
fetchPosts();
|
|
}, []);
|
|
|
|
const allTags = [...new Set(posts.flatMap(post => post.tags || []))];
|
|
|
|
const filteredPosts = posts
|
|
.filter(post => {
|
|
const title = post.title || '';
|
|
const author = post.author || '';
|
|
return title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
author.toLowerCase().includes(searchTerm.toLowerCase());
|
|
})
|
|
.filter(post =>
|
|
selectedTag ? (post.tags || []).includes(selectedTag) : true
|
|
);
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 py-12 max-w-5xl">
|
|
<div className="text-center mb-12">
|
|
<h1 className="text-5xl font-extrabold text-gray-900 leading-tight">OurWorld Blog</h1>
|
|
<p className="mt-4 text-xl text-gray-600 max-w-2xl mx-auto">
|
|
Insights, stories, and updates from the OurWorld Cooperative.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mb-8 flex flex-col md:flex-row gap-4 items-center">
|
|
<input
|
|
type="text"
|
|
placeholder="Search articles..."
|
|
className="flex-grow w-full md:w-auto px-4 py-2 border border-gray-300 rounded-full focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow"
|
|
value={searchTerm}
|
|
onChange={e => setSearchTerm(e.target.value)}
|
|
/>
|
|
<div className="relative w-full md:w-auto">
|
|
<select
|
|
className="w-full appearance-none bg-white border border-gray-300 rounded-full px-4 py-2 pr-8 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow"
|
|
value={selectedTag}
|
|
onChange={e => setSelectedTag(e.target.value)}
|
|
>
|
|
<option value="">All Topics</option>
|
|
{allTags.map(tag => (
|
|
<option key={tag} value={tag}>{tag}</option>
|
|
))}
|
|
</select>
|
|
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
|
<svg className="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
{loading ? (
|
|
<div className="text-center text-gray-500">Loading posts...</div>
|
|
) : filteredPosts.length > 0 ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
|
|
{filteredPosts.map(post => (
|
|
<Link to={`/blog/${post.slug}`} key={post.slug} className="group block bg-white rounded-xl shadow-lg hover:shadow-2xl transition-all duration-300 overflow-hidden transform hover:-translate-y-1">
|
|
{post.cover_image && (
|
|
<img src={post.cover_image} alt={post.title} className="w-full h-48 object-cover" />
|
|
)}
|
|
<div className="p-6">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
{post.tags && post.tags.map(tag => (
|
|
<span key={tag} className="bg-blue-100 text-blue-800 text-xs font-semibold px-3 py-1 rounded-full">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-2 group-hover:text-blue-600 transition-colors">
|
|
{post.title}
|
|
</h2>
|
|
<p className="text-gray-500 text-sm mb-4">
|
|
By {post.author} on {new Date(post.date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}
|
|
</p>
|
|
<p className="text-gray-700 leading-relaxed">
|
|
{post.summary && post.summary.length > 200 ? `${post.summary.substring(0, 200)}...` : post.summary}
|
|
</p>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="text-center text-gray-500 py-16">
|
|
<h2 className="text-2xl font-semibold mb-2">No posts found</h2>
|
|
<p>Try adjusting your search or filters.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Blog; |