"use client"; import Image from "next/image"; import Link from "next/link"; import React, { useEffect, useState } from "react"; import ReactMarkdown from "react-markdown"; import "./assets/main.css"; import { getApiUrl } from "./utils/getApiUrl"; import { pickStrapiImage } from "./utils/strapiImage"; async function getHomepageData() { const apiUrl = getApiUrl(); const fetchWithTimeout = async (url: string, options: RequestInit = {}) => { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); try { const response = await fetch(url, { ...options, signal: controller.signal, headers: { "Content-Type": "application/json", Accept: "application/json", ...options.headers, }, }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); throw error; } }; for (let attempt = 1; attempt <= 3; attempt++) { try { console.log( `🔄 [getHomepageData] Tentative ${attempt}/3 - URL: ${apiUrl}/api/homepages?populate=*` ); const response = await fetchWithTimeout( `${apiUrl}/api/homepages?populate=*` ); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); console.log("✅ [getHomepageData] Données récupérées avec succès"); return data.data?.[0] ?? null; } catch (error) { console.error(`❌ [getHomepageData] Erreur tentative ${attempt}:`, error); if (attempt === 3) { console.error("🚨 [getHomepageData] Toutes les tentatives ont échoué"); return null; } await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); } } return null; } /** * Trois axes éditoriaux de la home. Contenu hardcodé pour l'instant : simple * et modifiable ici. À porter vers un content-type Strapi dédié plus tard si * on veut l'éditer sans déploiement. */ const takeaways = [ { icon: "psychology", title: "Intelligence artificielle", body: "Intégration d'IA locale et d'assistants conversationnels en environnement souverain.", }, { icon: "terminal", title: "Développement web", body: "Next.js, Strapi, FastAPI — stack moderne de bout en bout, du CMS à la diffusion.", }, { icon: "school", title: "École 42", body: "Formation par projets, pédagogie par les pairs, progression autodidacte continue.", }, ]; export default function HomePage() { const [homepage, setHomepage] = useState(null); const apiUrl = getApiUrl(); useEffect(() => { getHomepageData().then((data) => setHomepage(data)); }, []); if (!homepage) { return (

Chargement de la page…

); } const title = homepage.title ?? "Titre par défaut"; const cv: string = homepage.cv ?? ""; const portraitPick = homepage.photo ? pickStrapiImage(apiUrl, homepage.photo, "hero") : null; const imageUrl = portraitPick?.src ?? (homepage.photo?.url ? `${apiUrl}${homepage.photo.url}` : null); return (
{/* Hero "feuillet de vellum" : carte principale Ă  85 % sur le wallpaper. */}
{/* Portrait avec frame primary (1 px d'air), remplace le cercle historique. */}
{imageUrl ? (
{`Portrait
) : (
Image indisponible
)}
Portfolio · Étudiant 42 Perpignan

{title}

{cv && (
{cv}
)}
Voir mes projets Me contacter
{/* Trois axes : cartes éditoriales, grille 3 colonnes desktop, stack mobile. */}
Ce qui m'anime

Trois axes de travail

{takeaways.map((item) => (

{item.title}

{item.body}

))}
{/* Pull-quote "Démarche" : carte vellum légère (opacité 65 %, sans ombre, radius tile) pour rester lisible sur wallpaper sans écraser la variation éditoriale voulue par DESIGN.md §5. Barre gauche primaire conservée. */}
Démarche

« Apprendre à construire, puis construire pour apprendre — chaque projet est une nouvelle pièce du métier. »

— Fernand Gras-Calvet
); }