From abc3a9095c3408a3adfc4fc4eab594450043f6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=A9o=20GUILBERT?= Date: Wed, 8 Jul 2026 09:36:56 +0200 Subject: [PATCH] adding the article suggestion at the end of the blog post --- .../blog/[slug]/_components/OtherArticles.tsx | 102 ++++++++++++++++++ app/[locale]/blog/[slug]/page.tsx | 12 ++- .../works/_components/OtherProjects.tsx | 14 +-- lib/blog/get-articles.ts | 11 ++ messages/en.json | 4 + messages/fr.json | 4 + 6 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 app/[locale]/blog/[slug]/_components/OtherArticles.tsx diff --git a/app/[locale]/blog/[slug]/_components/OtherArticles.tsx b/app/[locale]/blog/[slug]/_components/OtherArticles.tsx new file mode 100644 index 0000000..7637a99 --- /dev/null +++ b/app/[locale]/blog/[slug]/_components/OtherArticles.tsx @@ -0,0 +1,102 @@ +'use client'; + +import { useRef, useEffect } from 'react'; +import Link from 'next/link'; +import { useTranslations } from "next-intl"; + +type props = { + locale: string; + articles: { + slug: string; + title: string; + description: string; + cover?: string; + }[]; +}; + +export function OtherArticles({ locale, articles }: props) { + const ref = useRef(null); + const t = useTranslations('blogPage.otherArticles'); + + useEffect(() => { + const el = ref.current; + if (!el) return; + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + el.querySelectorAll('.reveal').forEach((node, i) => { + setTimeout(() => node.classList.add('visible'), i * 120); + }); + observer.unobserve(el); + } + }, + { threshold: 0.1 } + ); + observer.observe(el); + return () => observer.disconnect(); + }, []); + + if (!articles.length) return null; + + return ( +
+ {/* Divider */} +
+ + {/* Label */} +
+ + + {t('sectionLabel')} + +
+ +

+ {t('title')} +

+ +
+ {articles.map((article, i) => ( + + {/* Image */} +
+ {article.cover && ( + {article.title} + )} +
+ + {/* Text */} +
+
+

+ {article.title} +

+

+ {article.description} +

+
+ +
+ + ))} +
+
+ ); +} diff --git a/app/[locale]/blog/[slug]/page.tsx b/app/[locale]/blog/[slug]/page.tsx index 4561b70..54c391b 100644 --- a/app/[locale]/blog/[slug]/page.tsx +++ b/app/[locale]/blog/[slug]/page.tsx @@ -2,9 +2,10 @@ import React from "react"; import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { MDXRemote } from "next-mdx-remote/rsc"; -import { getArticleBySlug, getAllArticles, getAlternatesArticle } from "@/lib/blog/get-articles"; +import { getArticleBySlug, getAllArticles, getAlternatesArticle, getRandomOtherArticles } from "@/lib/blog/get-articles"; import { ArticleHero } from "@/app/[locale]/blog/[slug]/_components/ArticleHero"; import { ArticleSummary } from "@/app/[locale]/blog/[slug]/_components/ArticleSummary"; +import { OtherArticles } from "@/app/[locale]/blog/[slug]/_components/OtherArticles"; import { locales } from "@/middleware"; import Link from "next/link"; import { MermaidDiagram } from "@/components/mdx/MermaidDiagram"; @@ -144,6 +145,13 @@ export default async function ArticlePage({ params }: props) { const articleUrl = `${siteUrl}/${locale}/blog/${slug}`; + const otherArticles = getRandomOtherArticles(locale, slug, 2).map((item) => ({ + slug: item.slug, + title: item.title, + description: item.description, + cover: item.cover, + })); + const imageUrl = article.cover ? article.cover.startsWith("http") ? article.cover @@ -229,6 +237,8 @@ export default async function ArticlePage({ params }: props) { components={mdxComponents} /> + + ); diff --git a/app/[locale]/works/_components/OtherProjects.tsx b/app/[locale]/works/_components/OtherProjects.tsx index c33180d..3be1d40 100644 --- a/app/[locale]/works/_components/OtherProjects.tsx +++ b/app/[locale]/works/_components/OtherProjects.tsx @@ -10,7 +10,7 @@ type props = { slug: string; title: string; description: string; - cover: string; + cover?: string; }[]; }; @@ -65,11 +65,13 @@ export function OtherProjects({ locale, projects }: props) { > {/* Image */}
- {project.title} + {project.cover && ( + {project.title} + )}
{/* Text */} diff --git a/lib/blog/get-articles.ts b/lib/blog/get-articles.ts index 7f3f4ed..09b4500 100644 --- a/lib/blog/get-articles.ts +++ b/lib/blog/get-articles.ts @@ -45,6 +45,17 @@ export function getAllArticles(locale: string): Article[] { .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); } +export function getRandomOtherArticles(locale: string, excludeSlug: string, count: number): Article[] { + const candidates = getAllArticles(locale).filter((article) => article.slug !== excludeSlug); + + for (let i = candidates.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [candidates[i], candidates[j]] = [candidates[j], candidates[i]]; + } + + return candidates.slice(0, count); +} + export function getAlternatesArticle(article: Article) { if (!article.translationKey) { return {}; diff --git a/messages/en.json b/messages/en.json index b2d5e1f..05f1ded 100644 --- a/messages/en.json +++ b/messages/en.json @@ -82,6 +82,10 @@ "noArticles": "No articles yet.", "summary": { "label": "Summary" + }, + "otherArticles": { + "sectionLabel": "Other articles", + "title": "See also" } }, "projectPage": { diff --git a/messages/fr.json b/messages/fr.json index eadf8bb..2591ff7 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -82,6 +82,10 @@ "noArticles": "Aucun article pour le moment.", "summary": { "label": "En résumé" + }, + "otherArticles": { + "sectionLabel": "Autres articles", + "title": "Voir aussi" } }, "projectPage": {