mirror of
https://github.com/Ladebeze66/devsite.git
synced 2025-12-13 04:36:49 +01:00
test
This commit is contained in:
parent
8bf66f695d
commit
d45d36465c
@ -1,58 +1,71 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import Link from "next/link";
|
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
|
export default function Page() {
|
||||||
async function getAllCompetences() {
|
const [competences, setCompetences] = useState([]); // 🔥 Stocker les compétences une seule fois
|
||||||
try {
|
const apiUrl = getApiUrl(); // 🔥 Définition de l'URL API
|
||||||
const response = await fetch("http://localhost:1337/api/competences?populate=*");
|
|
||||||
if (!response.ok) {
|
useEffect(() => {
|
||||||
throw new Error("Failed to fetch competences");
|
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
|
fetchCompetences(); // 🔥 Exécuter une seule fois au montage du composant
|
||||||
export default async function Page() {
|
}, [apiUrl]); // ✅ Exécuter `useEffect()` uniquement si `apiUrl` change
|
||||||
const competences = await getAllCompetences();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="w-full p-6">
|
<main className="w-full p-3 mt-5 mb-5">
|
||||||
<h1 className="text-3xl mb-6 font-bold text-gray-700 text-center">Mes Compétences</h1>
|
{/* Titre de la page */}
|
||||||
|
<h1 className="text-3xl mb-3 font-bold text-gray-700 text-center">Mes Compétences</h1>
|
||||||
|
|
||||||
{/* Grille améliorée avec une meilleure gestion de l'espace */}
|
{/* Affichage d'un message si aucune compétence n'est trouvée */}
|
||||||
<div className="grid gap-4 grid-cols-[repeat(auto-fit,minmax(300px,1fr))] max-w-7xl mx-auto">
|
{competences.length === 0 ? (
|
||||||
{competences.map((competence) => {
|
<p className="text-center text-gray-500">Aucune compétence disponible.</p>
|
||||||
const picture = competence.picture?.[0];
|
) : (
|
||||||
const imageUrl = picture?.url ? `http://localhost:1337${picture.url}` : "/placeholder.jpg";
|
<div className="grid gap-7 grid-cols-[repeat(auto-fit,minmax(300px,1fr))] max-w-7xl mx-auto">
|
||||||
|
{competences.map((competence) => {
|
||||||
|
const picture = competence.picture?.[0];
|
||||||
|
const imageUrl = picture?.url ? `${apiUrl}${picture.url}` : "/placeholder.jpg";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={competence.id}
|
key={competence.id}
|
||||||
className="bg-white rounded-lg shadow-md overflow-hidden w-80 h-96 flex flex-col transform transition-all duration-300 hover:scale-105 hover:shadow-xl"
|
className="bg-white rounded-lg shadow-md overflow-hidden w-80 h-96 flex flex-col transform transition-all duration-300 hover:scale-105 hover:shadow-xl p-4"
|
||||||
>
|
>
|
||||||
<Link href={`/competences/${competence.slug}`}>
|
{/* Lien vers la page de détail de la compétence */}
|
||||||
<div className="overflow-hidden w-full h-48">
|
<Link href={`/competences/${competence.slug}`}>
|
||||||
<img
|
<div className="overflow-hidden w-full h-48 mb-4">
|
||||||
src={imageUrl}
|
<img
|
||||||
alt={picture?.name || "Competence image"}
|
src={imageUrl}
|
||||||
className="w-full h-full object-cover"
|
alt={picture?.name || "Competence image"}
|
||||||
/>
|
className="w-full h-full object-cover"
|
||||||
</div>
|
/>
|
||||||
<div className="p-4 flex-grow">
|
</div>
|
||||||
<p className="font-bold text-xl mb-2">{competence.name}</p>
|
<div className="flex-grow overflow-y-auto max-h-32 hide-scrollbar show-scrollbar">
|
||||||
<p className="text-gray-700 text-sm hover:text-base transition-all duration-200 ease-in-out">
|
<p className="font-bold text-xl mb-2">{competence.name}</p>
|
||||||
{competence.description}
|
<p className="text-gray-700 text-sm hover:text-base transition-all duration-200 ease-in-out">
|
||||||
</p>
|
{competence.description}
|
||||||
</div>
|
</p>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
);
|
</div>
|
||||||
})}
|
);
|
||||||
</div>
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,30 @@
|
|||||||
import { fetchData } from "../utils/fetchData"; // Importation de la fonction fetchData pour récupérer les données depuis l'API
|
"use client";
|
||||||
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
|
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
|
// Définition des propriétés du composant ContentSection
|
||||||
interface ContentSectionProps {
|
interface ContentSectionProps {
|
||||||
@ -11,21 +35,31 @@ interface ContentSectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Composant principal ContentSection
|
// Composant principal ContentSection
|
||||||
export default async function ContentSection({ collection, slug, titleClass, contentClass }: ContentSectionProps) {
|
export default function ContentSection({ collection, slug, titleClass, contentClass }: ContentSectionProps) {
|
||||||
// Récupération des données depuis l'API en utilisant la fonction fetchData
|
const [data, setData] = useState<ContentData | null>(null);
|
||||||
const data = await fetchData(collection, slug);
|
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
|
// Affichage d'un message si les données ne sont pas disponibles
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <div>Contenu introuvable.</div>;
|
return <div className="text-center text-gray-500">Contenu introuvable.</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Déstructuration des données récupérées
|
// Déstructuration des données récupérées
|
||||||
const { name, Resum: richText, picture, link, linkText } = data;
|
const { name, Resum: richText, picture, link, linkText } = data;
|
||||||
|
|
||||||
// Transformation des images de Strapi en format attendu par le carrousel
|
// Transformation des images de Strapi en format attendu par le carrousel
|
||||||
const images = picture?.map((img: any) => ({
|
const images = picture?.map((img: ImageData) => ({
|
||||||
url: `http://localhost:1337${img?.formats?.large?.url || img?.url}`, // Utilisation de l'URL de l'image en format large ou originale
|
url: `${apiUrl}${img.formats?.large?.url || img.url}`, // 🔥 URL dynamique
|
||||||
alt: img.name || "Image", // Texte alternatif pour l'image
|
alt: img.name || "Image", // Texte alternatif pour l'image
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
@ -57,4 +91,4 @@ export default async function ContentSection({ collection, slug, titleClass, con
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,60 +1,82 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import { getApiUrl } from "../utils/getApiUrl"; // ✅ Importation de l'URL dynamique
|
||||||
import CarouselCompetences from "./CarouselCompetences";
|
import CarouselCompetences from "./CarouselCompetences";
|
||||||
import ReactMarkdown from "react-markdown";
|
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";
|
import ModalGlossaire from "./ModalGlossaire";
|
||||||
|
|
||||||
// Définition des propriétés du composant ContentSectionCompetences
|
// ✅ Définition des types pour TypeScript
|
||||||
interface ContentSectionProps {
|
interface ImageData {
|
||||||
competenceData: any;
|
url: string;
|
||||||
glossaireData: any[];
|
formats?: {
|
||||||
titleClass?: string;
|
large?: { url: string };
|
||||||
contentClass?: string;
|
};
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompetenceData {
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
picture?: ImageData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Définition du type Glossaire
|
|
||||||
interface GlossaireItem {
|
interface GlossaireItem {
|
||||||
mot_clef: string;
|
mot_clef: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
variantes: string[];
|
variantes: string[];
|
||||||
description: 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) {
|
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<GlossaireItem | null>(null);
|
const [selectedMot, setSelectedMot] = useState<GlossaireItem | null>(null);
|
||||||
|
const apiUrl = getApiUrl(); // ✅ Détection automatique de l'URL API
|
||||||
|
|
||||||
if (!competenceData) {
|
if (!competenceData) {
|
||||||
|
console.error("❌ [ContentSectionCompetences] Compétence introuvable !");
|
||||||
return <div className="text-red-500 text-center">❌ Compétence introuvable.</div>;
|
return <div className="text-red-500 text-center">❌ Compétence introuvable.</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Déstructuration des données de la compétence
|
// ✅ Déstructuration des données de la compétence
|
||||||
const { name, content, picture } = competenceData;
|
const { name, content, picture } = competenceData;
|
||||||
|
|
||||||
// Transformation des images de Strapi en format attendu par le carrousel
|
// ✅ Transformation des images de Strapi en format attendu par le carrousel
|
||||||
const images = picture?.map((img: any) => ({
|
const images = picture?.map((img) => ({
|
||||||
url: `http://localhost:1337${img?.formats?.large?.url || img?.url}`,
|
url: `${apiUrl}${img.formats?.large?.url || img.url}`, // ✅ Correction ici
|
||||||
alt: img.name || "Image de compétence",
|
alt: img.name || "Image de compétence",
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
// 🔥 Transformation du texte riche avec des <span> cliquables
|
console.log("✅ [ContentSectionCompetences] Images préparées :", images);
|
||||||
|
|
||||||
|
// ✅ Transformation des mots-clés du glossaire
|
||||||
function transformMarkdownWithKeywords(text: string) {
|
function transformMarkdownWithKeywords(text: string) {
|
||||||
if (!glossaireData || glossaireData.length === 0) return text;
|
if (!glossaireData.length) return text;
|
||||||
|
|
||||||
let modifiedText = text;
|
let modifiedText = text;
|
||||||
|
|
||||||
glossaireData.forEach(({ mot_clef, variantes }) => {
|
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");
|
const regex = new RegExp(`\\b(${mot_clef}|${regexVariants})\\b`, "gi");
|
||||||
|
|
||||||
modifiedText = modifiedText.replace(regex, (match) => {
|
modifiedText = modifiedText.replace(regex, (match) => {
|
||||||
return `<span class="keyword" data-mot="${mot_clef}" style="color: blue; cursor: pointer;">${match}</span>`; // ✅ Span cliquable
|
return `<span class="keyword" data-mot="${mot_clef}" style="color: blue; cursor: pointer;">${match}</span>`;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("🔄 [ContentSectionCompetences] Contenu transformé avec mots-clés :", modifiedText);
|
||||||
return modifiedText;
|
return modifiedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +84,7 @@ export default function ContentSectionCompetences({ competenceData, glossaireDat
|
|||||||
|
|
||||||
// ✅ Gestion des clics sur les mots-clés
|
// ✅ Gestion des clics sur les mots-clés
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleKeywordClick(event: any) {
|
function handleKeywordClick(event: MouseEvent) {
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
if (target.classList.contains("keyword")) {
|
if (target.classList.contains("keyword")) {
|
||||||
const mot = target.getAttribute("data-mot");
|
const mot = target.getAttribute("data-mot");
|
||||||
@ -73,25 +95,18 @@ export default function ContentSectionCompetences({ competenceData, glossaireDat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("click", handleKeywordClick);
|
document.body.addEventListener("click", handleKeywordClick);
|
||||||
return () => document.removeEventListener("click", handleKeywordClick);
|
return () => document.body.removeEventListener("click", handleKeywordClick);
|
||||||
}, [glossaireData]);
|
}, [glossaireData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// ✅ Affichage de la compétence
|
|
||||||
<div className="max-w-3xl mx-auto p-6">
|
<div className="max-w-3xl mx-auto p-6">
|
||||||
{/* Titre de la section */}
|
{/* ✅ Ajout de logs visuels dans le rendu */}
|
||||||
<h1 className={titleClass || "text-3xl mb-6 font-bold text-gray-700"}>{name}</h1>
|
<h1 className={titleClass || "text-3xl mb-6 font-bold text-gray-700"}>{name}</h1>
|
||||||
|
|
||||||
{/* Carrousel pour afficher les images */}
|
|
||||||
<CarouselCompetences images={images} className="w-full h-64" />
|
<CarouselCompetences images={images} className="w-full h-64" />
|
||||||
|
|
||||||
{/* 🔥 Affichage du texte riche avec mots-clés cliquables */}
|
|
||||||
<div className={contentClass || "mt-6 text-lg text-black-700"}>
|
<div className={contentClass || "mt-6 text-lg text-black-700"}>
|
||||||
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{contentWithLinks}</ReactMarkdown> {/* ✅ Permet d'interpréter le HTML */}
|
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{contentWithLinks}</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 🚀 Modale pour afficher les infos des mots-clés */}
|
|
||||||
{selectedMot && <ModalGlossaire mot={selectedMot} onClose={() => setSelectedMot(null)} />}
|
{selectedMot && <ModalGlossaire mot={selectedMot} onClose={() => setSelectedMot(null)} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { fetchDataCompetences, fetchDataGlossaire } from "../utils/fetchDataCompetences";
|
import { fetchDataCompetences, fetchDataGlossaire } from "../utils/fetchDataCompetences";
|
||||||
import ContentSectionCompetences from "./ContentSectionCompetences";
|
import ContentSectionCompetences from "./ContentSectionCompetences";
|
||||||
|
|
||||||
// Définition des propriétés du composant ContentSection
|
|
||||||
interface ContentSectionProps {
|
interface ContentSectionProps {
|
||||||
collection: string;
|
collection: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@ -9,11 +8,15 @@ interface ContentSectionProps {
|
|||||||
contentClass?: string;
|
contentClass?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Composant principal ContentSection
|
|
||||||
export default async function ContentSectionCompetencesContainer({ collection, slug, titleClass, contentClass }: ContentSectionProps) {
|
export default async function ContentSectionCompetencesContainer({ collection, slug, titleClass, contentClass }: ContentSectionProps) {
|
||||||
const competenceData = await fetchDataCompetences(collection, slug);
|
console.log("🔍 [ContentSectionCompetencesContainer] Chargement des données...");
|
||||||
const glossaireData = await fetchDataGlossaire();
|
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<ContentSectionCompetences
|
<ContentSectionCompetences
|
||||||
competenceData={competenceData}
|
competenceData={competenceData}
|
||||||
|
|||||||
@ -1,19 +1,32 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { createPortal } from "react-dom"; // Insère la modale dans <body>
|
import { createPortal } from "react-dom"; // Insère la modale dans <body>
|
||||||
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
|
// ✅ Définition des propriétés du composant ModalGlossaire
|
||||||
interface ModalGlossaireProps {
|
interface ImageData {
|
||||||
mot: {
|
url: string;
|
||||||
mot_clef: string; // Mot-clé du glossaire
|
formats?: {
|
||||||
description: string; // Description du mot-clé
|
large?: { url: string };
|
||||||
images?: any[]; // Images associées au mot-clé
|
|
||||||
};
|
};
|
||||||
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) {
|
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
|
// Désactiver le scroll du `body` quand la modale est ouverte
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.classList.add("overflow-hidden");
|
document.body.classList.add("overflow-hidden");
|
||||||
@ -25,13 +38,11 @@ export default function ModalGlossaire({ mot, onClose }: ModalGlossaireProps) {
|
|||||||
// Debug : Vérifier les images reçues
|
// Debug : Vérifier les images reçues
|
||||||
console.log("🖼️ Images reçues dans la modale :", mot.images);
|
console.log("🖼️ Images reçues dans la modale :", mot.images);
|
||||||
|
|
||||||
// Vérifier si `mot.images` est bien un tableau et contient des images
|
// ✅ Vérification et mise à jour des URLs d'image avec `getApiUrl()`
|
||||||
const images = mot.images?.map((img: any) => {
|
const images = mot.images?.map((img) => ({
|
||||||
return {
|
url: `${apiUrl}${img.formats?.large?.url || img.url}`,
|
||||||
url: `http://localhost:1337${img.formats?.large?.url || img.url}`,
|
alt: img.name || "Illustration",
|
||||||
alt: img.name || "Illustration",
|
})) || [];
|
||||||
};
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div className="fixed inset-0 w-screen h-screen bg-black bg-opacity-75 flex items-center justify-center z-[1000]">
|
<div className="fixed inset-0 w-screen h-screen bg-black bg-opacity-75 flex items-center justify-center z-[1000]">
|
||||||
@ -57,4 +68,4 @@ export default function ModalGlossaire({ mot, onClose }: ModalGlossaireProps) {
|
|||||||
</div>,
|
</div>,
|
||||||
document.body
|
document.body
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
38
app/page.tsx
38
app/page.tsx
@ -1,40 +1,43 @@
|
|||||||
import React from "react";
|
"use client";
|
||||||
import ReactMarkdown from "react-markdown"; // Importation de ReactMarkdown
|
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
import "./assets/main.css";
|
import "./assets/main.css";
|
||||||
|
import { getApiUrl } from "./utils/getApiUrl"; // 🔥 Import de l'URL dynamique
|
||||||
|
|
||||||
async function getHomepageData() {
|
async function getHomepageData() {
|
||||||
|
const apiUrl = getApiUrl(); // 🔥 Utilisation de l'URL centralisée
|
||||||
try {
|
try {
|
||||||
const response = await fetch("http://localhost:1337/api/homepages?populate=*");
|
const response = await fetch(`${apiUrl}/api/homepages?populate=*`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch homepage content");
|
throw new Error("Failed to fetch homepage content");
|
||||||
}
|
}
|
||||||
const homepage = await response.json();
|
const data = await response.json();
|
||||||
return homepage.data?.[0]; // On récupère la première entrée
|
return data.data?.[0] ?? null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching homepage:", error);
|
console.error("Error fetching homepage:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default function HomePage() {
|
||||||
const homepage = await getHomepageData();
|
const [homepage, setHomepage] = useState<any>(null);
|
||||||
|
const apiUrl = getApiUrl();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getHomepageData().then((data) => setHomepage(data));
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!homepage) return <p className="text-center text-red-500">Erreur lors du chargement du contenu.</p>;
|
if (!homepage) return <p className="text-center text-red-500">Erreur lors du chargement du contenu.</p>;
|
||||||
|
|
||||||
// Récupération des données
|
const title = homepage.title ?? "Titre par défaut";
|
||||||
const title = homepage?.title;
|
const cv = homepage.cv ?? "";
|
||||||
const cv = homepage?.cv || ""; // Assurer que `cv` est une chaîne même si vide
|
const imageUrl = homepage.photo?.url ? `${apiUrl}${homepage.photo.url}` : null;
|
||||||
const photo = homepage?.photo;
|
|
||||||
|
|
||||||
// Correction de l'URL de l'image
|
|
||||||
const baseUrl = "http://localhost:1337";
|
|
||||||
const imageUrl = photo?.url ? `${baseUrl}${photo.url}` : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-3xl w-full mx-auto flex flex-col items-center justify-center p-6 bg-white/55 rounded-lg mt-12 mb-3">
|
<main className="max-w-3xl w-full mx-auto flex flex-col items-center justify-center p-6 bg-white/55 rounded-lg mt-12 mb-3">
|
||||||
{/* Texte court (title) */}
|
|
||||||
<h1 className="text-3xl font-bold text-gray-800 mb-4">{title}</h1>
|
<h1 className="text-3xl font-bold text-gray-800 mb-4">{title}</h1>
|
||||||
|
|
||||||
{/* Photo en cadre ovale avec effet hover */}
|
|
||||||
{imageUrl ? (
|
{imageUrl ? (
|
||||||
<div className="relative w-64 h-64 rounded-full overflow-hidden shadow-lg border-4 border-gray-300 transition-transform duration-300 hover:scale-110 hover:rotate-3">
|
<div className="relative w-64 h-64 rounded-full overflow-hidden shadow-lg border-4 border-gray-300 transition-transform duration-300 hover:scale-110 hover:rotate-3">
|
||||||
<img src={imageUrl} alt="Photo de profil" className="w-full h-full object-cover object-center" />
|
<img src={imageUrl} alt="Photo de profil" className="w-full h-full object-cover object-center" />
|
||||||
@ -45,7 +48,6 @@ export default async function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Texte riche en Markdown */}
|
|
||||||
<div className="mt-6 text-lg text-black-700 max-w-2xl px-6 text-center">
|
<div className="mt-6 text-lg text-black-700 max-w-2xl px-6 text-center">
|
||||||
<ReactMarkdown>{cv}</ReactMarkdown>
|
<ReactMarkdown>{cv}</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,51 +1,11 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useParams } from "next/navigation";
|
|
||||||
import ContentSection from "../../components/ContentSection";
|
import ContentSection from "../../components/ContentSection";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page({ params }: { params: { slug: string } }) {
|
||||||
const params = useParams();
|
const slug = params.slug;
|
||||||
const [data, setData] = useState(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState<string | null>(null); // ✅ Ajout du typage string | null
|
|
||||||
const slug = typeof params.slug === "string" ? params.slug : "";
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (!slug) {
|
||||||
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) {
|
|
||||||
return <div className="text-red-500 text-center">❌ Erreur : Slug introuvable.</div>;
|
return <div className="text-red-500 text-center">❌ Erreur : Slug introuvable.</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
return <ContentSection collection="projects" slug={slug} />;
|
||||||
return <div className="text-blue-500 text-center">⏳ Chargement...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <div className="text-red-500 text-center">{error}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return params.slug ? <ContentSection collection="projects" slug={slug} /> : <div className="text-red-500 text-center">❌ Erreur : Slug introuvable.</div>;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,30 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import Link from "next/link";
|
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
|
export default function Page() {
|
||||||
async function getAllprojects() {
|
const [projects, setProjects] = useState([]); // 🔥 Stocker les projets une seule fois
|
||||||
try {
|
const apiUrl = getApiUrl(); // 🔥 Définition de l'URL API
|
||||||
const response = await fetch("http://localhost:1337/api/projects?populate=*");
|
|
||||||
if (!response.ok) {
|
useEffect(() => {
|
||||||
throw new Error("Failed to fetch projects");
|
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
|
fetchProjects(); // 🔥 Exécuter une seule fois au montage du composant
|
||||||
export default async function Page() {
|
}, [apiUrl]); // ✅ Exécuter `useEffect()` uniquement si `apiUrl` change
|
||||||
const projects = await getAllprojects();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="w-full p-3 mt-5 mb-5">
|
<main className="w-full p-3 mt-5 mb-5">
|
||||||
@ -28,7 +35,7 @@ export default async function Page() {
|
|||||||
<div className="grid gap-7 grid-cols-[repeat(auto-fit,minmax(300px,1fr))] max-w-7xl mx-auto">
|
<div className="grid gap-7 grid-cols-[repeat(auto-fit,minmax(300px,1fr))] max-w-7xl mx-auto">
|
||||||
{projects.map((project) => {
|
{projects.map((project) => {
|
||||||
const picture = project.picture?.[0];
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -57,4 +64,4 @@ export default async function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
3
app/utils/config.ts
Normal file
3
app/utils/config.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// utils/config.ts
|
||||||
|
|
||||||
|
export const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:1337";
|
||||||
@ -1,7 +1,10 @@
|
|||||||
import qs from "qs"; // Importation de qs pour construire des requêtes de chaîne de requête
|
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
|
// Fonction pour récupérer des données spécifiques depuis l'API Strapi
|
||||||
export async function fetchData(collection: string, slug: string) {
|
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
|
// Construction de la requête avec des filtres et des relations à peupler
|
||||||
const query = qs.stringify({
|
const query = qs.stringify({
|
||||||
filters: { slug }, // Filtre basé sur le slug
|
filters: { slug }, // Filtre basé sur le slug
|
||||||
@ -9,14 +12,17 @@ export async function fetchData(collection: string, slug: string) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
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
|
// 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
|
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
|
// Vérification de la réponse de l'API
|
||||||
if (!response.ok) {
|
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
|
// 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
|
return data.data[0] || null; // Retourne la première entrée ou null si aucune donnée n'est trouvée
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Gestion des erreurs et log des erreurs
|
// 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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
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({
|
const query = qs.stringify({
|
||||||
filters: {
|
filters: { slug: { $eq: slug } },
|
||||||
slug: { $eq: slug },
|
populate: "picture",
|
||||||
},
|
|
||||||
populate: "picture", // On garde les images des compétences
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log de la requête API pour le débogage
|
const fullUrl = `${apiUrl}/api/${collection}?${query}`;
|
||||||
console.log(`🛠️ Requête API Compétence : http://localhost:1337/api/${collection}?${query}`);
|
console.log("🔍 [fetchDataCompetences] Requête API :", fullUrl);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Envoi de la requête à l'API Strapi
|
const response = await fetch(fullUrl, { cache: "no-store" });
|
||||||
const response = await fetch(`http://localhost:1337/api/${collection}?${query}`, {
|
|
||||||
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) {
|
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();
|
const data = await response.json();
|
||||||
console.log("✅ Données reçues (Compétence) :", data);
|
console.log("✅ [fetchDataCompetences] Données reçues :", JSON.stringify(data, null, 2));
|
||||||
|
return data.data?.[0] ?? null;
|
||||||
// Retourne la première compétence ou null si aucune donnée n'est trouvée
|
|
||||||
return data.data[0] || null;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Gestion des erreurs et log des erreurs
|
console.error("❌ [fetchDataCompetences] Erreur lors de la récupération des compétences :", error);
|
||||||
console.error("❌ Erreur lors de la récupération des compétences :", error);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fonction pour récupérer les données du glossaire
|
|
||||||
export async function fetchDataGlossaire() {
|
export async function fetchDataGlossaire() {
|
||||||
|
const apiUrl = getApiUrl();
|
||||||
|
const fullUrl = `${apiUrl}/api/glossaires?populate=images`;
|
||||||
|
|
||||||
|
console.log("🔍 [fetchDataGlossaire] Requête API :", fullUrl);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Log de la requête API pour le débogage
|
const response = await fetch(fullUrl, { cache: "no-store" });
|
||||||
console.log("🛠️ Requête API Glossaire : http://localhost:1337/api/glossaires?populate=images");
|
|
||||||
|
|
||||||
// Envoi de la requête à l'API Strapi
|
console.log(`📡 [fetchDataGlossaire] Réponse HTTP : ${response.status} ${response.statusText}`);
|
||||||
const response = await fetch("http://localhost:1337/api/glossaires?populate=images", {
|
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Vérification de la réponse de l'API
|
|
||||||
if (!response.ok) {
|
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();
|
const data = await response.json();
|
||||||
console.log("✅ Données reçues (Glossaire) :", data);
|
console.log("✅ [fetchDataGlossaire] Données reçues :", JSON.stringify(data, null, 2));
|
||||||
|
return data.data ?? [];
|
||||||
// Retourne les données du glossaire ou un tableau vide si aucune donnée n'est trouvée
|
|
||||||
return data.data || [];
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Gestion des erreurs et log des erreurs
|
console.error("❌ [fetchDataGlossaire] Erreur lors de la récupération du glossaire :", error);
|
||||||
console.error("❌ Erreur lors de la récupération du glossaire :", error);
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
app/utils/getApiUrl.ts
Normal file
19
app/utils/getApiUrl.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,26 +1,28 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @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() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: "/api/:path*", // Toute URL commençant par /api
|
source: "/api/:path*",
|
||||||
destination: "http://localhost:1337/api/:path*", // Redirige vers votre backend Strapi
|
destination: process.env.NEXT_PUBLIC_API_URL + "/api/:path*",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
// Optimisation des fichiers statiques
|
|
||||||
images: {
|
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;
|
||||||
|
|
||||||
|
|||||||
13
package-lock.json
generated
13
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strapi/blocks-react-renderer": "^1.0.1",
|
"@strapi/blocks-react-renderer": "^1.0.1",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"next": "^15.1.6",
|
"next": "^15.1.6",
|
||||||
"qs": "^6.14.0",
|
"qs": "^6.14.0",
|
||||||
@ -1243,6 +1244,18 @@
|
|||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strapi/blocks-react-renderer": "^1.0.1",
|
"@strapi/blocks-react-renderer": "^1.0.1",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"next": "^15.1.6",
|
"next": "^15.1.6",
|
||||||
"qs": "^6.14.0",
|
"qs": "^6.14.0",
|
||||||
|
|||||||
BIN
public/.well-known/acme-challenge/test-www.txt
Normal file
BIN
public/.well-known/acme-challenge/test-www.txt
Normal file
Binary file not shown.
BIN
public/.well-known/acme-challenge/test.txt
Normal file
BIN
public/.well-known/acme-challenge/test.txt
Normal file
Binary file not shown.
26
web.config
Normal file
26
web.config
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<system.webServer>
|
||||||
|
<rewrite>
|
||||||
|
<outboundRules>
|
||||||
|
<rule name="ReverseProxyOutboundRule1" preCondition="ResponseIsHtml1">
|
||||||
|
<match filterByTags="A, Form, Img" pattern="^http(s)?://http://localhost:3000/(.*)" />
|
||||||
|
<action type="Rewrite" value="http{R:1}://localhost/{R:2}" />
|
||||||
|
</rule>
|
||||||
|
<preConditions>
|
||||||
|
<preCondition name="ResponseIsHtml1">
|
||||||
|
<add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
|
||||||
|
</preCondition>
|
||||||
|
</preConditions>
|
||||||
|
</outboundRules>
|
||||||
|
<rules>
|
||||||
|
<rule name="ReverseProxyInboundRule1" stopProcessing="true">
|
||||||
|
<match url="(.*)" />
|
||||||
|
<action type="Rewrite" url="http://localhost:3000/{R:1}" />
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</rewrite>
|
||||||
|
<directoryBrowse enabled="true" />
|
||||||
|
<caching enableKernelCache="true" />
|
||||||
|
</system.webServer>
|
||||||
|
</configuration>
|
||||||
Loading…
x
Reference in New Issue
Block a user