diff --git a/app/Competences/page.jsx b/app/Competences/page.jsx index 29cd3b5..7ea4b2c 100644 --- a/app/Competences/page.jsx +++ b/app/Competences/page.jsx @@ -1,58 +1,71 @@ +"use client"; + +import { useEffect, useState } from "react"; import Link from "next/link"; +import { getApiUrl } from "../utils/getApiUrl"; // đŸ”„ Import de l'URL dynamique -// Fonction pour rĂ©cupĂ©rer toutes les compĂ©tences depuis l'API Strapi -async function getAllCompetences() { - try { - const response = await fetch("http://localhost:1337/api/competences?populate=*"); - if (!response.ok) { - throw new Error("Failed to fetch competences"); +export default function Page() { + const [competences, setCompetences] = useState([]); // đŸ”„ Stocker les compĂ©tences une seule fois + const apiUrl = getApiUrl(); // đŸ”„ DĂ©finition de l'URL API + + useEffect(() => { + async function fetchCompetences() { + console.log("🔍 API utilisĂ©e pour les compĂ©tences :", apiUrl); + try { + const response = await fetch(`${apiUrl}/api/competences?populate=*`); + if (!response.ok) { + throw new Error(`Erreur de rĂ©cupĂ©ration des compĂ©tences : ${response.statusText}`); + } + const data = await response.json(); + setCompetences(data.data ?? []); + } catch (error) { + console.error("❌ Erreur lors de la rĂ©cupĂ©ration des compĂ©tences :", error); + } } - const competences = await response.json(); - return competences.data; - } catch (error) { - console.error("Error fetching competences:", error); - return []; - } -} -// Composant principal de la page des compĂ©tences -export default async function Page() { - const competences = await getAllCompetences(); + fetchCompetences(); // đŸ”„ ExĂ©cuter une seule fois au montage du composant + }, [apiUrl]); // ✅ ExĂ©cuter `useEffect()` uniquement si `apiUrl` change return ( -
-

Mes Compétences

+
+ {/* Titre de la page */} +

Mes Compétences

- {/* Grille améliorée avec une meilleure gestion de l'espace */} -
- {competences.map((competence) => { - const picture = competence.picture?.[0]; - const imageUrl = picture?.url ? `http://localhost:1337${picture.url}` : "/placeholder.jpg"; + {/* Affichage d'un message si aucune compétence n'est trouvée */} + {competences.length === 0 ? ( +

Aucune compétence disponible.

+ ) : ( +
+ {competences.map((competence) => { + const picture = competence.picture?.[0]; + const imageUrl = picture?.url ? `${apiUrl}${picture.url}` : "/placeholder.jpg"; - return ( -
- -
- {picture?.name -
-
-

{competence.name}

-

- {competence.description} -

-
- -
- ); - })} -
+ return ( +
+ {/* Lien vers la page de détail de la compétence */} + +
+ {picture?.name +
+
+

{competence.name}

+

+ {competence.description} +

+
+ +
+ ); + })} +
+ )}
); } diff --git a/app/components/ContentSection.tsx b/app/components/ContentSection.tsx index e420bbb..18e3f9f 100644 --- a/app/components/ContentSection.tsx +++ b/app/components/ContentSection.tsx @@ -1,6 +1,30 @@ -import { fetchData } from "../utils/fetchData"; // Importation de la fonction fetchData pour rĂ©cupĂ©rer les donnĂ©es depuis l'API -import Carousel from "./Carousel"; // Importation du composant Carousel pour afficher les images -import ReactMarkdown from "react-markdown"; // Importation de ReactMarkdown pour rendre le texte riche en Markdown +"use client"; + +import { useEffect, useState } from "react"; +import { fetchData } from "../utils/fetchData"; // Importation de la fonction fetchData +import { getApiUrl } from "../utils/getApiUrl"; // Importation de l'URL dynamique +import Carousel from "./Carousel"; // Importation du composant Carrousel +import ReactMarkdown from "react-markdown"; // Importation pour gĂ©rer le Markdown + +// DĂ©finition du type pour une image +interface ImageData { + url: string; + formats?: { + large?: { + url: string; + }; + }; + name?: string; +} + +// DĂ©finition du type pour les donnĂ©es rĂ©cupĂ©rĂ©es +interface ContentData { + name: string; + Resum: string; // Texte en Markdown + picture?: ImageData[]; + link?: string; + linkText?: string; +} // DĂ©finition des propriĂ©tĂ©s du composant ContentSection interface ContentSectionProps { @@ -11,21 +35,31 @@ interface ContentSectionProps { } // Composant principal ContentSection -export default async function ContentSection({ collection, slug, titleClass, contentClass }: ContentSectionProps) { - // RĂ©cupĂ©ration des donnĂ©es depuis l'API en utilisant la fonction fetchData - const data = await fetchData(collection, slug); +export default function ContentSection({ collection, slug, titleClass, contentClass }: ContentSectionProps) { + const [data, setData] = useState(null); + const apiUrl = getApiUrl(); // DĂ©tection automatique de l'URL de l'API + + useEffect(() => { + async function fetchContent() { + console.log("🔍 API utilisĂ©e pour ContentSection :", apiUrl); + const result = await fetchData(collection, slug); + setData(result); + } + + fetchContent(); + }, [collection, slug, apiUrl]); // Affichage d'un message si les donnĂ©es ne sont pas disponibles if (!data) { - return
Contenu introuvable.
; + return
Contenu introuvable.
; } // DĂ©structuration des donnĂ©es rĂ©cupĂ©rĂ©es const { name, Resum: richText, picture, link, linkText } = data; // Transformation des images de Strapi en format attendu par le carrousel - const images = picture?.map((img: any) => ({ - url: `http://localhost:1337${img?.formats?.large?.url || img?.url}`, // Utilisation de l'URL de l'image en format large ou originale + const images = picture?.map((img: ImageData) => ({ + url: `${apiUrl}${img.formats?.large?.url || img.url}`, // đŸ”„ URL dynamique alt: img.name || "Image", // Texte alternatif pour l'image })) || []; @@ -57,4 +91,4 @@ export default async function ContentSection({ collection, slug, titleClass, con )} ); -} \ No newline at end of file +} diff --git a/app/components/ContentSectionCompetences.tsx b/app/components/ContentSectionCompetences.tsx index 2eb4847..1a03854 100644 --- a/app/components/ContentSectionCompetences.tsx +++ b/app/components/ContentSectionCompetences.tsx @@ -1,60 +1,82 @@ "use client"; import { useState, useEffect } from "react"; +import { getApiUrl } from "../utils/getApiUrl"; // ✅ Importation de l'URL dynamique import CarouselCompetences from "./CarouselCompetences"; import ReactMarkdown from "react-markdown"; -import rehypeRaw from "rehype-raw"; // ✅ Permet d'interprĂ©ter le HTML dans ReactMarkdown +import rehypeRaw from "rehype-raw"; import ModalGlossaire from "./ModalGlossaire"; -// DĂ©finition des propriĂ©tĂ©s du composant ContentSectionCompetences -interface ContentSectionProps { - competenceData: any; - glossaireData: any[]; - titleClass?: string; - contentClass?: string; +// ✅ DĂ©finition des types pour TypeScript +interface ImageData { + url: string; + formats?: { + large?: { url: string }; + }; + name?: string; +} + +interface CompetenceData { + name: string; + content: string; + picture?: ImageData[]; } -// ✅ DĂ©finition du type Glossaire interface GlossaireItem { mot_clef: string; slug: string; variantes: string[]; description: string; - images?: any[]; + images?: ImageData[]; } -// Composant principal ContentSectionCompetences +interface ContentSectionProps { + competenceData: CompetenceData | null; + glossaireData: GlossaireItem[]; + titleClass?: string; + contentClass?: string; +} + +// ✅ Composant principal export default function ContentSectionCompetences({ competenceData, glossaireData, titleClass, contentClass }: ContentSectionProps) { + console.log("🔍 [ContentSectionCompetences] Chargement du composant..."); + console.log("📌 [ContentSectionCompetences] DonnĂ©es reçues - competenceData :", competenceData); + console.log("📌 [ContentSectionCompetences] DonnĂ©es reçues - glossaireData :", glossaireData); + const [selectedMot, setSelectedMot] = useState(null); + const apiUrl = getApiUrl(); // ✅ DĂ©tection automatique de l'URL API if (!competenceData) { + console.error("❌ [ContentSectionCompetences] CompĂ©tence introuvable !"); return
❌ CompĂ©tence introuvable.
; } - // DĂ©structuration des donnĂ©es de la compĂ©tence + // ✅ DĂ©structuration des donnĂ©es de la compĂ©tence const { name, content, picture } = competenceData; - // Transformation des images de Strapi en format attendu par le carrousel - const images = picture?.map((img: any) => ({ - url: `http://localhost:1337${img?.formats?.large?.url || img?.url}`, + // ✅ Transformation des images de Strapi en format attendu par le carrousel + const images = picture?.map((img) => ({ + url: `${apiUrl}${img.formats?.large?.url || img.url}`, // ✅ Correction ici alt: img.name || "Image de compĂ©tence", })) || []; - // đŸ”„ Transformation du texte riche avec des cliquables + console.log("✅ [ContentSectionCompetences] Images prĂ©parĂ©es :", images); + + // ✅ Transformation des mots-clĂ©s du glossaire function transformMarkdownWithKeywords(text: string) { - if (!glossaireData || glossaireData.length === 0) return text; + if (!glossaireData.length) return text; let modifiedText = text; - glossaireData.forEach(({ mot_clef, variantes }) => { - const regexVariants = (variantes || []).map((v: string) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"); + const regexVariants = variantes.map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"); const regex = new RegExp(`\\b(${mot_clef}|${regexVariants})\\b`, "gi"); modifiedText = modifiedText.replace(regex, (match) => { - return `${match}`; // ✅ Span cliquable + return `${match}`; }); }); + console.log("🔄 [ContentSectionCompetences] Contenu transformĂ© avec mots-clĂ©s :", modifiedText); return modifiedText; } @@ -62,7 +84,7 @@ export default function ContentSectionCompetences({ competenceData, glossaireDat // ✅ Gestion des clics sur les mots-clĂ©s useEffect(() => { - function handleKeywordClick(event: any) { + function handleKeywordClick(event: MouseEvent) { const target = event.target as HTMLElement; if (target.classList.contains("keyword")) { const mot = target.getAttribute("data-mot"); @@ -73,25 +95,18 @@ export default function ContentSectionCompetences({ competenceData, glossaireDat } } - document.addEventListener("click", handleKeywordClick); - return () => document.removeEventListener("click", handleKeywordClick); + document.body.addEventListener("click", handleKeywordClick); + return () => document.body.removeEventListener("click", handleKeywordClick); }, [glossaireData]); return ( - // ✅ Affichage de la compĂ©tence
- {/* Titre de la section */} + {/* ✅ Ajout de logs visuels dans le rendu */}

{name}

- - {/* Carrousel pour afficher les images */} - - {/* đŸ”„ Affichage du texte riche avec mots-clĂ©s cliquables */}
- {contentWithLinks} {/* ✅ Permet d'interprĂ©ter le HTML */} + {contentWithLinks}
- - {/* 🚀 Modale pour afficher les infos des mots-clĂ©s */} {selectedMot && setSelectedMot(null)} />}
); diff --git a/app/components/ContentSectionCompetencesContainer.tsx b/app/components/ContentSectionCompetencesContainer.tsx index fe7bb9f..6bb439e 100644 --- a/app/components/ContentSectionCompetencesContainer.tsx +++ b/app/components/ContentSectionCompetencesContainer.tsx @@ -1,7 +1,6 @@ import { fetchDataCompetences, fetchDataGlossaire } from "../utils/fetchDataCompetences"; import ContentSectionCompetences from "./ContentSectionCompetences"; -// DĂ©finition des propriĂ©tĂ©s du composant ContentSection interface ContentSectionProps { collection: string; slug: string; @@ -9,11 +8,15 @@ interface ContentSectionProps { contentClass?: string; } -// Composant principal ContentSection export default async function ContentSectionCompetencesContainer({ collection, slug, titleClass, contentClass }: ContentSectionProps) { - const competenceData = await fetchDataCompetences(collection, slug); - const glossaireData = await fetchDataGlossaire(); + console.log("🔍 [ContentSectionCompetencesContainer] Chargement des donnĂ©es..."); + const competenceData = await fetchDataCompetences(collection, slug); + console.log("✅ [ContentSectionCompetencesContainer] DonnĂ©es compĂ©tences :", JSON.stringify(competenceData, null, 2)); + + const glossaireData = await fetchDataGlossaire(); + console.log("✅ [ContentSectionCompetencesContainer] DonnĂ©es glossaire :", JSON.stringify(glossaireData, null, 2)); + return ( -import CarouselCompetences from "./CarouselCompetences"; // Importation du composant CarouselCompetences pour afficher les images +import { getApiUrl } from "../utils/getApiUrl"; // ✅ Import de l'URL dynamique +import CarouselCompetences from "./CarouselCompetences"; // Importation du composant CarouselCompetences -// DĂ©finition des propriĂ©tĂ©s du composant ModalGlossaire -interface ModalGlossaireProps { - mot: { - mot_clef: string; // Mot-clĂ© du glossaire - description: string; // Description du mot-clĂ© - images?: any[]; // Images associĂ©es au mot-clĂ© +// ✅ DĂ©finition des propriĂ©tĂ©s du composant ModalGlossaire +interface ImageData { + url: string; + formats?: { + large?: { url: string }; }; - onClose: () => void; // Fonction pour fermer la modale + name?: string; } -// Composant principal ModalGlossaire +interface GlossaireMot { + mot_clef: string; // Mot-clĂ© du glossaire + description: string; // Description du mot-clĂ© + images?: ImageData[]; // Images associĂ©es au mot-clĂ© +} + +interface ModalGlossaireProps { + mot: GlossaireMot; + onClose: () => void; +} + +// ✅ Composant principal ModalGlossaire export default function ModalGlossaire({ mot, onClose }: ModalGlossaireProps) { + const apiUrl = getApiUrl(); // đŸ”„ DĂ©tection automatique de l'URL API + // DĂ©sactiver le scroll du `body` quand la modale est ouverte useEffect(() => { document.body.classList.add("overflow-hidden"); @@ -25,13 +38,11 @@ export default function ModalGlossaire({ mot, onClose }: ModalGlossaireProps) { // Debug : VĂ©rifier les images reçues console.log("đŸ–Œïž Images reçues dans la modale :", mot.images); - // VĂ©rifier si `mot.images` est bien un tableau et contient des images - const images = mot.images?.map((img: any) => { - return { - url: `http://localhost:1337${img.formats?.large?.url || img.url}`, - alt: img.name || "Illustration", - }; - }) || []; + // ✅ VĂ©rification et mise Ă  jour des URLs d'image avec `getApiUrl()` + const images = mot.images?.map((img) => ({ + url: `${apiUrl}${img.formats?.large?.url || img.url}`, + alt: img.name || "Illustration", + })) || []; return createPortal(
@@ -57,4 +68,4 @@ export default function ModalGlossaire({ mot, onClose }: ModalGlossaireProps) {
, document.body ); -} \ No newline at end of file +} diff --git a/app/page.tsx b/app/page.tsx index 34013bb..6ca10ed 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,40 +1,43 @@ -import React from "react"; -import ReactMarkdown from "react-markdown"; // Importation de ReactMarkdown +"use client"; + +import React, { useEffect, useState } from "react"; +import ReactMarkdown from "react-markdown"; import "./assets/main.css"; +import { getApiUrl } from "./utils/getApiUrl"; // đŸ”„ Import de l'URL dynamique async function getHomepageData() { + const apiUrl = getApiUrl(); // đŸ”„ Utilisation de l'URL centralisĂ©e try { - const response = await fetch("http://localhost:1337/api/homepages?populate=*"); + const response = await fetch(`${apiUrl}/api/homepages?populate=*`); if (!response.ok) { throw new Error("Failed to fetch homepage content"); } - const homepage = await response.json(); - return homepage.data?.[0]; // On rĂ©cupĂšre la premiĂšre entrĂ©e + const data = await response.json(); + return data.data?.[0] ?? null; } catch (error) { console.error("Error fetching homepage:", error); return null; } } -export default async function HomePage() { - const homepage = await getHomepageData(); +export default function HomePage() { + const [homepage, setHomepage] = useState(null); + const apiUrl = getApiUrl(); + + useEffect(() => { + getHomepageData().then((data) => setHomepage(data)); + }, []); + if (!homepage) return

Erreur lors du chargement du contenu.

; - // RĂ©cupĂ©ration des donnĂ©es - const title = homepage?.title; - const cv = homepage?.cv || ""; // Assurer que `cv` est une chaĂźne mĂȘme si vide - const photo = homepage?.photo; - - // Correction de l'URL de l'image - const baseUrl = "http://localhost:1337"; - const imageUrl = photo?.url ? `${baseUrl}${photo.url}` : null; + const title = homepage.title ?? "Titre par dĂ©faut"; + const cv = homepage.cv ?? ""; + const imageUrl = homepage.photo?.url ? `${apiUrl}${homepage.photo.url}` : null; return (
- {/* Texte court (title) */}

{title}

- {/* Photo en cadre ovale avec effet hover */} {imageUrl ? (
Photo de profil @@ -45,7 +48,6 @@ export default async function HomePage() {
)} - {/* Texte riche en Markdown */}
{cv}
diff --git a/app/portfolio/[slug]/page.tsx b/app/portfolio/[slug]/page.tsx index 0fa0d81..4175d24 100644 --- a/app/portfolio/[slug]/page.tsx +++ b/app/portfolio/[slug]/page.tsx @@ -1,51 +1,11 @@ -"use client"; - -import { useParams } from "next/navigation"; import ContentSection from "../../components/ContentSection"; -import { useEffect, useState } from "react"; -export default function Page() { - const params = useParams(); - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); // ✅ Ajout du typage string | null - const slug = typeof params.slug === "string" ? params.slug : ""; +export default function Page({ params }: { params: { slug: string } }) { + const slug = params.slug; - useEffect(() => { - if (!params?.slug) return; - - async function fetchData() { - try { - const response = await fetch(`http://localhost:1337/api/projects?filters[slug][$eq]=${params.slug}&populate=*`); - const jsonData = await response.json(); - - if (!jsonData?.data || jsonData.data.length === 0) { - setError("❌ Erreur : Projet introuvable."); - } else { - setData(jsonData.data); - } - } catch (err) { - setError("❌ Erreur de chargement des donnĂ©es."); - } finally { - setLoading(false); - } - } - - fetchData(); - }, [params.slug]); - - if (!params?.slug) { + if (!slug) { return
❌ Erreur : Slug introuvable.
; } - if (loading) { - return
⏳ Chargement...
; - } - - if (error) { - return
{error}
; - } - - return params.slug ? :
❌ Erreur : Slug introuvable.
; - + return ; } diff --git a/app/portfolio/page.jsx b/app/portfolio/page.jsx index 62ea58d..e7ae065 100644 --- a/app/portfolio/page.jsx +++ b/app/portfolio/page.jsx @@ -1,23 +1,30 @@ +"use client"; + +import { useEffect, useState } from "react"; import Link from "next/link"; +import { getApiUrl } from "../utils/getApiUrl"; // đŸ”„ Import de l'URL dynamique -// Fonction pour rĂ©cupĂ©rer tous les projets depuis l'API Strapi -async function getAllprojects() { - try { - const response = await fetch("http://localhost:1337/api/projects?populate=*"); - if (!response.ok) { - throw new Error("Failed to fetch projects"); +export default function Page() { + const [projects, setProjects] = useState([]); // đŸ”„ Stocker les projets une seule fois + const apiUrl = getApiUrl(); // đŸ”„ DĂ©finition de l'URL API + + useEffect(() => { + async function fetchProjects() { + console.log("🔍 API utilisĂ©e pour les projets :", apiUrl); + try { + const response = await fetch(`${apiUrl}/api/projects?populate=*`); + if (!response.ok) { + throw new Error(`Erreur de rĂ©cupĂ©ration des projets : ${response.statusText}`); + } + const data = await response.json(); + setProjects(data.data ?? []); + } catch (error) { + console.error("❌ Erreur lors de la rĂ©cupĂ©ration des projets :", error); + } } - const projects = await response.json(); - return projects.data; - } catch (error) { - console.error("Error fetching projects:", error); - return []; - } -} -// Composant principal de la page des projets -export default async function Page() { - const projects = await getAllprojects(); + fetchProjects(); // đŸ”„ ExĂ©cuter une seule fois au montage du composant + }, [apiUrl]); // ✅ ExĂ©cuter `useEffect()` uniquement si `apiUrl` change return (
@@ -28,7 +35,7 @@ export default async function Page() {
{projects.map((project) => { const picture = project.picture?.[0]; - const imageUrl = picture?.url ? `http://localhost:1337${picture.url}` : "/placeholder.jpg"; + const imageUrl = picture?.url ? `${apiUrl}${picture.url}` : "/placeholder.jpg"; return (
); -} \ No newline at end of file +} diff --git a/app/utils/config.ts b/app/utils/config.ts new file mode 100644 index 0000000..318c555 --- /dev/null +++ b/app/utils/config.ts @@ -0,0 +1,3 @@ +// utils/config.ts + +export const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:1337"; diff --git a/app/utils/fetchData.ts b/app/utils/fetchData.ts index 36b8026..089ee8a 100644 --- a/app/utils/fetchData.ts +++ b/app/utils/fetchData.ts @@ -1,7 +1,10 @@ import qs from "qs"; // Importation de qs pour construire des requĂȘtes de chaĂźne de requĂȘte +import { getApiUrl } from "./getApiUrl"; // đŸ”„ Import de l'URL dynamique // Fonction pour rĂ©cupĂ©rer des donnĂ©es spĂ©cifiques depuis l'API Strapi export async function fetchData(collection: string, slug: string) { + const apiUrl = getApiUrl(); // đŸ”„ DĂ©tection automatique de l'URL (local ou HTTPS) + // Construction de la requĂȘte avec des filtres et des relations Ă  peupler const query = qs.stringify({ filters: { slug }, // Filtre basĂ© sur le slug @@ -9,14 +12,17 @@ export async function fetchData(collection: string, slug: string) { }); try { + const fullUrl = `${apiUrl}/api/${collection}?${query}`; // đŸ”„ URL finale + console.log(`🔍 RequĂȘte API vers : ${fullUrl}`); // Log pour vĂ©rifier l'URL + // Envoi de la requĂȘte Ă  l'API Strapi - const response = await fetch(`http://localhost:1337/api/${collection}?${query}`, { + const response = await fetch(fullUrl, { cache: "no-store", // DĂ©sactivation du cache pour obtenir les donnĂ©es les plus rĂ©centes }); // VĂ©rification de la rĂ©ponse de l'API if (!response.ok) { - throw new Error("Failed to fetch data"); + throw new Error(`Erreur HTTP ${response.status} : ${response.statusText}`); } // RĂ©cupĂ©ration des donnĂ©es de la rĂ©ponse @@ -24,7 +30,7 @@ export async function fetchData(collection: string, slug: string) { return data.data[0] || null; // Retourne la premiĂšre entrĂ©e ou null si aucune donnĂ©e n'est trouvĂ©e } catch (error) { // Gestion des erreurs et log des erreurs - console.error(`Error fetching ${collection} data:`, error); + console.error(`❌ Erreur lors de la rĂ©cupĂ©ration des donnĂ©es (${collection}):`, error); return null; } -} \ No newline at end of file +} diff --git a/app/utils/fetchDataCompetences.ts b/app/utils/fetchDataCompetences.ts index 1fa7924..0c5ef3d 100644 --- a/app/utils/fetchDataCompetences.ts +++ b/app/utils/fetchDataCompetences.ts @@ -1,67 +1,56 @@ -import qs from "qs"; // Importation de qs pour construire des requĂȘtes de chaĂźne de requĂȘte +import qs from "qs"; +import { getApiUrl } from "./getApiUrl"; -// Fonction pour rĂ©cupĂ©rer une compĂ©tence spĂ©cifique export async function fetchDataCompetences(collection: string, slug: string) { - // Construction de la requĂȘte avec des filtres et des relations Ă  peupler + const apiUrl = getApiUrl(); const query = qs.stringify({ - filters: { - slug: { $eq: slug }, - }, - populate: "picture", // On garde les images des compĂ©tences + filters: { slug: { $eq: slug } }, + populate: "picture", }); - // Log de la requĂȘte API pour le dĂ©bogage - console.log(`đŸ› ïž RequĂȘte API CompĂ©tence : http://localhost:1337/api/${collection}?${query}`); + const fullUrl = `${apiUrl}/api/${collection}?${query}`; + console.log("🔍 [fetchDataCompetences] RequĂȘte API :", fullUrl); try { - // Envoi de la requĂȘte Ă  l'API Strapi - const response = await fetch(`http://localhost:1337/api/${collection}?${query}`, { - cache: "no-store", - }); + const response = await fetch(fullUrl, { cache: "no-store" }); + + console.log(`📡 [fetchDataCompetences] RĂ©ponse HTTP : ${response.status} ${response.statusText}`); - // VĂ©rification de la rĂ©ponse de l'API if (!response.ok) { - throw new Error(`Failed to fetch competences data: ${response.status}`); + console.error(`❌ [fetchDataCompetences] Erreur HTTP ${response.status} : ${response.statusText}`); + return null; } - // RĂ©cupĂ©ration des donnĂ©es de la rĂ©ponse const data = await response.json(); - console.log("✅ DonnĂ©es reçues (CompĂ©tence) :", data); - - // Retourne la premiĂšre compĂ©tence ou null si aucune donnĂ©e n'est trouvĂ©e - return data.data[0] || null; + console.log("✅ [fetchDataCompetences] DonnĂ©es reçues :", JSON.stringify(data, null, 2)); + return data.data?.[0] ?? null; } catch (error) { - // Gestion des erreurs et log des erreurs - console.error("❌ Erreur lors de la rĂ©cupĂ©ration des compĂ©tences :", error); + console.error("❌ [fetchDataCompetences] Erreur lors de la rĂ©cupĂ©ration des compĂ©tences :", error); return null; } } -// Fonction pour rĂ©cupĂ©rer les donnĂ©es du glossaire export async function fetchDataGlossaire() { + const apiUrl = getApiUrl(); + const fullUrl = `${apiUrl}/api/glossaires?populate=images`; + + console.log("🔍 [fetchDataGlossaire] RequĂȘte API :", fullUrl); + try { - // Log de la requĂȘte API pour le dĂ©bogage - console.log("đŸ› ïž RequĂȘte API Glossaire : http://localhost:1337/api/glossaires?populate=images"); + const response = await fetch(fullUrl, { cache: "no-store" }); - // Envoi de la requĂȘte Ă  l'API Strapi - const response = await fetch("http://localhost:1337/api/glossaires?populate=images", { - cache: "no-store", - }); + console.log(`📡 [fetchDataGlossaire] RĂ©ponse HTTP : ${response.status} ${response.statusText}`); - // VĂ©rification de la rĂ©ponse de l'API if (!response.ok) { - throw new Error(`Failed to fetch glossaire data: ${response.status}`); + console.error(`❌ [fetchDataGlossaire] Erreur HTTP ${response.status} : ${response.statusText}`); + return []; } - // RĂ©cupĂ©ration des donnĂ©es de la rĂ©ponse const data = await response.json(); - console.log("✅ DonnĂ©es reçues (Glossaire) :", data); - - // Retourne les donnĂ©es du glossaire ou un tableau vide si aucune donnĂ©e n'est trouvĂ©e - return data.data || []; + console.log("✅ [fetchDataGlossaire] DonnĂ©es reçues :", JSON.stringify(data, null, 2)); + return data.data ?? []; } catch (error) { - // Gestion des erreurs et log des erreurs - console.error("❌ Erreur lors de la rĂ©cupĂ©ration du glossaire :", error); + console.error("❌ [fetchDataGlossaire] Erreur lors de la rĂ©cupĂ©ration du glossaire :", error); return []; } -} \ No newline at end of file +} diff --git a/app/utils/getApiUrl.ts b/app/utils/getApiUrl.ts new file mode 100644 index 0000000..a5a2043 --- /dev/null +++ b/app/utils/getApiUrl.ts @@ -0,0 +1,19 @@ +export function getApiUrl() { + if (typeof window !== "undefined") { + // đŸ”„ DĂ©tection du mode local cĂŽtĂ© client + const isLocalhost = + window.location.hostname === "localhost" || + window.location.hostname === "127.0.0.1" || + window.location.hostname.startsWith("192.168.") || + window.location.hostname.endsWith(".local"); + + console.log("🌍 [getApiUrl] Mode CLIENT dĂ©tectĂ© - URL :", isLocalhost ? "http://localhost:1337" : "https://api.fernandgrascalvet.com"); + return isLocalhost ? "http://localhost:1337" : "https://api.fernandgrascalvet.com"; + } + + // đŸ”„ CĂŽtĂ© serveur (SSR), on utilise une variable d'environnement + const apiUrl = process.env.NEXT_PUBLIC_API_URL || "https://api.fernandgrascalvet.com"; + console.log("🌍 [getApiUrl] Mode SERVEUR dĂ©tectĂ© - URL :", apiUrl); + return apiUrl; + } + \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index e77b6cf..711e41d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,26 +1,28 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - // Active le mode strict de React pour signaler des erreurs potentielles - reactStrictMode: true, - experimental: { - appDir: true, // ✅ Assurez-vous que cette ligne est bien prĂ©sente - }, - // Gestion des réécritures d'URL pour proxy local vers le backend +require("dotenv").config(); + +console.log("🔍 VĂ©rification NEXT_PUBLIC_API_URL:", process.env.NEXT_PUBLIC_API_URL); + + +const nextConfig = { + reactStrictMode: true, + compress: false, // ❌ DĂ©sactive la compression Gzip pour Ă©viter les erreurs IIS + trailingSlash: true, + + // Utilisation de l'API URL dynamique pour Strapi async rewrites() { return [ { - source: "/api/:path*", // Toute URL commençant par /api - destination: "http://localhost:1337/api/:path*", // Redirige vers votre backend Strapi + source: "/api/:path*", + destination: process.env.NEXT_PUBLIC_API_URL + "/api/:path*", }, ]; }, - // Optimisation des fichiers statiques images: { - domains: ["localhost"], // Permet de charger les images provenant de "localhost" si nĂ©cessaire + domains: ["localhost", "api.fernandgrascalvet.com"], // ✅ Autorise aussi l'API en HTTPS }, }; -export default nextConfig; - +module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index 84fb6af..703b3e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@strapi/blocks-react-renderer": "^1.0.1", "@tailwindcss/typography": "^0.5.16", + "dotenv": "^16.4.7", "husky": "^9.1.7", "next": "^15.1.6", "qs": "^6.14.0", @@ -1243,6 +1244,18 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index 7af0d56..a78d7fd 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@strapi/blocks-react-renderer": "^1.0.1", "@tailwindcss/typography": "^0.5.16", + "dotenv": "^16.4.7", "husky": "^9.1.7", "next": "^15.1.6", "qs": "^6.14.0", diff --git a/public/.well-known/acme-challenge/test-www.txt b/public/.well-known/acme-challenge/test-www.txt new file mode 100644 index 0000000..b48605d Binary files /dev/null and b/public/.well-known/acme-challenge/test-www.txt differ diff --git a/public/.well-known/acme-challenge/test.txt b/public/.well-known/acme-challenge/test.txt new file mode 100644 index 0000000..b48605d Binary files /dev/null and b/public/.well-known/acme-challenge/test.txt differ diff --git a/web.config b/web.config new file mode 100644 index 0000000..5d69404 --- /dev/null +++ b/web.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +