"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; import { fetchData } from "../utils/fetchData"; import { getApiUrl } from "../utils/getApiUrl"; import { pickStrapiImage, type StrapiMediaLike } from "../utils/strapiImage"; import Carousel from "./Carousel"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; interface ImageData extends StrapiMediaLike { name?: string; } interface ContentData { name: string; /** * Champ richtext Markdown de la fiche. * * Dette historique : le content-type Strapi `project` utilise `Resum` avec * majuscule (legacy Strapi 4). Les nouveaux content-types (ex. * `realisation-ia`) utilisent `resum` en minuscule, cohérent avec tous les * autres champs (`name`, `slug`, `link`, `order`…). On tolère les deux * orthographes dans le rendu pour ne pas avoir à renommer `Resum` côté * `project` (ce qui casserait les 15+ fiches projet déjà saisies). */ Resum?: string; resum?: string; picture?: ImageData[]; link?: string; linkText?: string; } interface ContentSectionProps { collection: string; slug: string; titleClass?: string; contentClass?: string; /** * Lien du bouton retour discret posé en haut de la page. * Défaut : `/portfolio` (comportement historique pour les fiches projet). */ backHref?: string; /** * Libellé du bouton retour. Défaut : `"Portfolio"`. */ backLabel?: string; /** * Kicker affiché au-dessus du titre dans l'en-tête vellum. * Défaut : `"Projet · Portfolio"` (fiches du portfolio). * Exemple pour une réalisation de compétence : `"Réalisation · Compétence IA"`. */ kickerLabel?: string; /** * Message affiché dans l'état 404 (fiche introuvable). * Défaut : `"Ce projet est introuvable."` */ notFoundLabel?: string; } /** * Fiche détail portfolio — refonte "Digital Atelier" (étape 7.b). * * Structure : bouton retour + en-tête vellum (kicker + titre Manrope) + carousel * détail (Swiper Stitch) + corps Markdown en `prose` Newsreader + CTA jewel * optionnel vers le lien externe. Les props `titleClass` / `contentClass` * héritées du composant pré-refonte restent acceptées pour compatibilité mais * sont ignorées (styles tokenisés désormais) — on les garde dans l'interface * pour ne pas casser les consommateurs. * * 2026-04-23 : composant rendu paramétrable pour être réutilisé par la page * détail des réalisations IA (`/competences/[slug]/[realisation]`) avec un * retour vers la compétence parente au lieu du portfolio. Les 4 props * `backHref` / `backLabel` / `kickerLabel` / `notFoundLabel` ont des défauts * strictement identiques au comportement historique → 100 % rétro-compatible * pour les fiches projet existantes. */ export default function ContentSection({ collection, slug, backHref = "/portfolio", backLabel = "Portfolio", kickerLabel = "Projet · Portfolio", notFoundLabel = "Ce projet est introuvable.", }: ContentSectionProps) { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const apiUrl = getApiUrl(); useEffect(() => { async function fetchContent() { try { const result = await fetchData(collection, slug); setData(result); } finally { setIsLoading(false); } } fetchContent(); }, [collection, slug, apiUrl]); if (isLoading) { return (
); } if (!data) { return (

{notFoundLabel}

Retour
); } const { name, picture, link, linkText } = data; // Legacy `Resum` (content-type `project`) OU `resum` moderne (nouveaux content-types). const richText = data.Resum ?? data.resum ?? ""; const images = picture ?.map((img: ImageData) => { const picked = pickStrapiImage(apiUrl, img, "full"); const url = picked?.src ?? (img.url || img.formats?.large?.url ? `${apiUrl}${img.formats?.large?.url ?? img.url}` : null); if (!url) return null; return { url, alt: img.name || `Visuel du projet ${name}`, }; }) .filter((item): item is { url: string; alt: string } => item != null) || []; return (
{/* Bouton retour discret, posé sur le wallpaper comme une miette de fil d'Ariane. */} {backLabel} {/* En-tête "feuillet de vellum" aligné sur la home et les listes. */}
{kickerLabel}

{name}

{images.length > 0 && (
)} {richText && (
{richText}
)} {link && ( )}
); }