Related Article section implemented
This commit is contained in:
+8
-23
@@ -1,8 +1,8 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { getBlogPostBySlug, getAllPosts } from "../../../lib/contentProcessor";
|
||||
import ContentThumbnailTemplate from "../../components/ContentThumbnailTemplate";
|
||||
import ContentBanner from "../../components/ContentBanner";
|
||||
import RelatedArticles from "../../components/RelatedArticles";
|
||||
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
|
||||
|
||||
/**
|
||||
@@ -70,9 +70,9 @@ export default async function BlogPostPage({ params }) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Get related posts (for now, just get other posts)
|
||||
// Get related articles (for now, just get other posts)
|
||||
const allPosts = getAllPosts();
|
||||
const relatedPosts = allPosts.filter((p) => p.slug !== post.slug).slice(0, 3); // Show up to 3 related posts
|
||||
const relatedArticles = allPosts; // Pass all posts to RelatedArticles component for filtering
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#F4F3F1] relative overflow-hidden">
|
||||
@@ -122,26 +122,11 @@ export default async function BlogPostPage({ params }) {
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{/* Related Posts Section */}
|
||||
{relatedPosts.length > 0 && (
|
||||
<section className="bg-white border-t border-gray-200">
|
||||
<div className="max-w-6xl mx-auto px-4 py-12">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-8 text-center">
|
||||
Related Articles
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{relatedPosts.map((relatedPost) => (
|
||||
<ContentThumbnailTemplate
|
||||
key={relatedPost.slug}
|
||||
post={relatedPost}
|
||||
variant="vertical"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
{/* Related Articles Section */}
|
||||
<RelatedArticles
|
||||
relatedPosts={relatedArticles}
|
||||
currentPostSlug={post.slug}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,13 +14,16 @@ const ContentContainer = ({ post, width = "200px", size = "responsive" }) => {
|
||||
|
||||
if (!slug) return icons[0];
|
||||
|
||||
// Use the same hash logic as background images to ensure matching
|
||||
const hash = slug.split("").reduce((a, b) => {
|
||||
a = (a << 5) - a + b.charCodeAt(0);
|
||||
return a & a;
|
||||
}, 0);
|
||||
|
||||
return icons[Math.abs(hash) % icons.length];
|
||||
// Use the same cycling logic as background images to ensure matching
|
||||
const slugOrder = [
|
||||
"building-community-trust",
|
||||
"operational-security-mutual-aid",
|
||||
"making-decisions-without-hierarchy",
|
||||
"resolving-active-conflicts",
|
||||
];
|
||||
const index = slugOrder.indexOf(slug);
|
||||
const finalIndex = index >= 0 ? index % icons.length : 0;
|
||||
return icons[finalIndex];
|
||||
};
|
||||
|
||||
const iconImage = getIconImage(post.slug);
|
||||
|
||||
@@ -32,16 +32,16 @@ const ContentThumbnailTemplate = ({
|
||||
|
||||
if (!slug) return images[0];
|
||||
|
||||
// Use the slug to deterministically select an image
|
||||
// More robust hash function using djb2 algorithm
|
||||
let hash = 5381;
|
||||
for (let i = 0; i < slug.length; i++) {
|
||||
hash = (hash << 5) + hash + slug.charCodeAt(i);
|
||||
}
|
||||
|
||||
// Ensure positive number and get index
|
||||
const index = Math.abs(hash) % images.length;
|
||||
return images[index];
|
||||
// Simple cycling approach to ensure different styles
|
||||
const slugOrder = [
|
||||
"building-community-trust",
|
||||
"operational-security-mutual-aid",
|
||||
"making-decisions-without-hierarchy",
|
||||
"resolving-active-conflicts",
|
||||
];
|
||||
const index = slugOrder.indexOf(slug);
|
||||
const finalIndex = index >= 0 ? index % images.length : 0;
|
||||
return images[finalIndex];
|
||||
};
|
||||
|
||||
const backgroundImage = getBackgroundImage(post.slug, variant);
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import ContentThumbnailTemplate from "./ContentThumbnailTemplate";
|
||||
|
||||
export default function RelatedArticles({ relatedPosts, currentPostSlug }) {
|
||||
// Filter out the current post from related posts
|
||||
const filteredPosts = relatedPosts.filter(
|
||||
(post) => post.slug !== currentPostSlug
|
||||
);
|
||||
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
// Auto-advance every 3 seconds
|
||||
useEffect(() => {
|
||||
if (filteredPosts.length <= 1) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setProgress(0);
|
||||
setCurrentIndex((prev) => (prev + 1) % filteredPosts.length);
|
||||
}, 3000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [filteredPosts.length]);
|
||||
|
||||
// Progress animation
|
||||
useEffect(() => {
|
||||
if (filteredPosts.length <= 1) return;
|
||||
|
||||
const progressInterval = setInterval(() => {
|
||||
setProgress((prev) => {
|
||||
if (prev >= 100) {
|
||||
return 0;
|
||||
}
|
||||
return prev + 1;
|
||||
});
|
||||
}, 30); // 30ms intervals for smooth animation
|
||||
|
||||
return () => clearInterval(progressInterval);
|
||||
}, [currentIndex, filteredPosts.length]);
|
||||
|
||||
if (filteredPosts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="py-[var(--spacing-scale-032)]">
|
||||
<div className="flex flex-col gap-[var(--spacing-scale-032)]">
|
||||
<h2 className="text-[32px] leading-[110%] font-medium text-[var(--color-content-inverse-primary)] text-center">
|
||||
Related Articles
|
||||
</h2>
|
||||
|
||||
{/* Horizontal Articles Row - All Visible with Centering */}
|
||||
<div className="flex justify-center overflow-hidden">
|
||||
<div
|
||||
className="flex gap-0 transition-transform duration-500 ease-in-out"
|
||||
style={{
|
||||
transform: `translateX(calc(50% - 130px - ${
|
||||
currentIndex * 260
|
||||
}px))`,
|
||||
}}
|
||||
>
|
||||
{filteredPosts.map((relatedPost, index) => (
|
||||
<div
|
||||
key={relatedPost.slug}
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<ContentThumbnailTemplate
|
||||
post={relatedPost}
|
||||
variant="vertical"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Three separate progress bars below the carousel */}
|
||||
<div className="flex justify-center gap-[var(--measures-spacing-008)] px-[var(--measures-spacing-064)]">
|
||||
{filteredPosts.map((relatedPost, index) => (
|
||||
<div
|
||||
key={relatedPost.slug}
|
||||
className="max-w-[var(--measures-spacing-056)] w-full h-[var(--measures-spacing-004)] bg-gray-200 rounded-full overflow-hidden"
|
||||
>
|
||||
<div
|
||||
className="h-full bg-gray-600 rounded-full transition-all duration-75 ease-linear"
|
||||
style={{
|
||||
width:
|
||||
index === currentIndex
|
||||
? `${progress}%`
|
||||
: index < currentIndex
|
||||
? "100%"
|
||||
: "0%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user