MAJ gestion des pages services
This commit is contained in:
@@ -172,11 +172,11 @@ const Header = () => {
|
|||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/services/structure-beton-charpente-metallique-bois"
|
to="/services/formations-web"
|
||||||
onClick={handleMenuClose}
|
onClick={handleMenuClose}
|
||||||
>
|
>
|
||||||
<WorkIcon sx={{ marginRight: 1 }} />
|
<WorkIcon sx={{ marginRight: 1 }} />
|
||||||
Vidéos entreprise
|
Formations web
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -375,11 +375,11 @@ const Header = () => {
|
|||||||
<ListItem
|
<ListItem
|
||||||
button
|
button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/services/structure-beton-charpente-metallique-bois"
|
to="/services/formations-web"
|
||||||
selected={location.pathname.includes("/services/structure-beton-charpente-metallique-bois")}
|
selected={location.pathname.includes("/services/formations-web")}
|
||||||
>
|
>
|
||||||
<WorkIcon sx={{ marginRight: 0.5 }} />
|
<WorkIcon sx={{ marginRight: 0.5 }} />
|
||||||
<ListItemText primary="Vidéos entreprise..." />
|
<ListItemText primary="Formations web..." />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -27,18 +27,46 @@ const ServiceCinq = () => {
|
|||||||
return <p>Chargement...</p>;
|
return <p>Chargement...</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStoredValue = (key) =>
|
||||||
|
typeof window !== "undefined" ? window.localStorage.getItem(key) : null;
|
||||||
|
|
||||||
|
const storedHeroBackground = getStoredValue(`hero_bg_${pageId}`);
|
||||||
|
const storedHeroImage = getStoredValue(`hero_img_${pageId}`);
|
||||||
|
|
||||||
|
const heroBackground =
|
||||||
|
acfData?.hero_background ||
|
||||||
|
acfData?.background_hero ||
|
||||||
|
acfData?.hero_bg ||
|
||||||
|
storedHeroBackground ||
|
||||||
|
"linear-gradient(135deg, #050d1f 0%, #02050d 85%)";
|
||||||
|
|
||||||
|
const heroImage =
|
||||||
|
acfData?.hero_background_image ||
|
||||||
|
acfData?.hero_background_image_url ||
|
||||||
|
acfData?.hero_image_url ||
|
||||||
|
acfData?.hero_image ||
|
||||||
|
acfData?.image_hero ||
|
||||||
|
acfData?.img_hero_url ||
|
||||||
|
acfData?.img_hero ||
|
||||||
|
storedHeroImage ||
|
||||||
|
"https://picsum.photos/id/1026/1920/1080.webp";
|
||||||
|
|
||||||
const serviceDetails = {
|
const serviceDetails = {
|
||||||
// Hero
|
// Hero
|
||||||
title: acfData?.titre_principal_tce || "Titre héros !", // ✅ Modifiable individuellement
|
title: acfData?.titre_principal_tce || "Titre héros !", // ✅ Modifiable individuellement
|
||||||
subtitle: <span dangerouslySetInnerHTML={{ __html: acfData?.description_principal_tce || "Description, héros" }} />,
|
subtitle:
|
||||||
|
acfData?.description_principal_tce ||
|
||||||
|
"<p>Description, héros</p>",
|
||||||
|
|
||||||
image: "https://picsum.photos/id/1026/1920/1080.webp",
|
image: heroImage,
|
||||||
|
heroBackground,
|
||||||
ctaText: "En savoir plus",
|
ctaText: "En savoir plus",
|
||||||
ctaLink: "/contact",
|
ctaLink: "/contact",
|
||||||
|
|
||||||
// section 1
|
// section 1
|
||||||
interestTitle: acfData?.second_titre_tce || "Titre interet", // ✅ Modifiable individuellement
|
interestTitle: acfData?.second_titre_tce || "Titre interet", // ✅ Modifiable individuellement
|
||||||
description: <span dangerouslySetInnerHTML={{ __html: acfData?.second_description_tce || "Description interet" }} />,
|
description:
|
||||||
|
acfData?.second_description_tce || "<p>Description interet</p>",
|
||||||
// section 2
|
// section 2
|
||||||
desirTitle: "Titre", // ✅ Modifiable individuellement
|
desirTitle: "Titre", // ✅ Modifiable individuellement
|
||||||
|
|
||||||
|
|||||||
@@ -25,18 +25,48 @@ const ServiceDeux = () => {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return <p>Chargement...</p>;
|
return <p>Chargement...</p>;
|
||||||
}
|
}
|
||||||
const serviceDetails = {
|
const getStoredValue = (key) =>
|
||||||
// Hero
|
typeof window !== "undefined" ? window.localStorage.getItem(key) : null;
|
||||||
title: acfData?.titre_principal_beton || "Structure béton charpente métallique & bois",
|
|
||||||
subtitle: <span dangerouslySetInnerHTML={{ __html: acfData?.description_principal_beton || "Solidité, Résistance, Durabilité"}} />,
|
|
||||||
|
|
||||||
image: "https://picsum.photos/id/240/1920/1080.webp",
|
const storedHeroBackground = getStoredValue(`hero_bg_${pageId}`);
|
||||||
|
const storedHeroImage = getStoredValue(`hero_img_${pageId}`);
|
||||||
|
|
||||||
|
const heroBackground =
|
||||||
|
acfData?.hero_background ||
|
||||||
|
acfData?.background_hero ||
|
||||||
|
acfData?.hero_bg ||
|
||||||
|
storedHeroBackground ||
|
||||||
|
"linear-gradient(135deg, #041226 0%, #01040a 90%)";
|
||||||
|
|
||||||
|
const heroImage =
|
||||||
|
acfData?.hero_background_image ||
|
||||||
|
acfData?.hero_background_image_url ||
|
||||||
|
acfData?.hero_image_url ||
|
||||||
|
acfData?.hero_image ||
|
||||||
|
acfData?.image_hero ||
|
||||||
|
acfData?.img_hero_url ||
|
||||||
|
acfData?.img_hero ||
|
||||||
|
storedHeroImage ||
|
||||||
|
"https://picsum.photos/id/240/1920/1080.webp";
|
||||||
|
|
||||||
|
const serviceDetails = {
|
||||||
|
|
||||||
|
// Hero
|
||||||
|
title: acfData?.titre_principal_beton || "formations-web",
|
||||||
|
subtitle:
|
||||||
|
acfData?.description_principal_beton ||
|
||||||
|
"<p>Solidité, Résistance, Durabilité</p>",
|
||||||
|
|
||||||
|
image: heroImage,
|
||||||
|
heroBackground,
|
||||||
ctaText: "En savoir plus !",
|
ctaText: "En savoir plus !",
|
||||||
ctaLink: "/contact",
|
ctaLink: "/contact",
|
||||||
|
|
||||||
// section 1
|
// section 1
|
||||||
interestTitle: acfData?.second_titre_beton ||"Béton ou Charpente :\n Quel Matériau Choisir pour une Construction Durable ?", // ✅ Modifiable individuellement
|
interestTitle: acfData?.second_titre_beton ||"Béton ou Charpente :\n Quel Matériau Choisir pour une Construction Durable ?", // ✅ Modifiable individuellement
|
||||||
description: <span dangerouslySetInnerHTML={{ __html: acfData?.second_description_beton || "Le béton est le matériau de construction le plus répandu dans le monde. \nIl est présent dans tous les secteurs de la construction.\n\n Longtemps associé à l’image négative des grands ensembles, gris et vieillissants, le béton a réalisé des progrès spectaculaires ces dernières années, tant au niveau de ses performances techniques que des aspects esthétiques. \n\nIl commence même à entrer dans les foyers comme produit de décoration !\nUne charpente est un assemblage de pièces de bois et/ou de métal, servant à soutenir ou couvrir des constructions et faisant partie de la toiture."}} />,
|
description:
|
||||||
|
acfData?.second_description_beton ||
|
||||||
|
"<p>Le béton est le matériau de construction le plus répandu dans le monde. Il est présent dans tous les secteurs de la construction.</p><p>Longtemps associé à l’image négative des grands ensembles, gris et vieillissants, le béton a réalisé des progrès spectaculaires ces dernières années, tant au niveau de ses performances techniques que des aspects esthétiques.</p><p>Il commence même à entrer dans les foyers comme produit de décoration ! Une charpente est un assemblage de pièces de bois et/ou de métal, servant à soutenir ou couvrir des constructions et faisant partie de la toiture.</p>",
|
||||||
|
|
||||||
// section 2
|
// section 2
|
||||||
desirTitle: acfData?.troisieme_titre_beton || "Titre",// ✅ Modifiable individuellement
|
desirTitle: acfData?.troisieme_titre_beton || "Titre",// ✅ Modifiable individuellement
|
||||||
|
|||||||
@@ -3,15 +3,6 @@ import { Helmet } from "react-helmet-async"; // ✅ SEO
|
|||||||
import ServicePageTemplate from "../ServicePageTemplate";
|
import ServicePageTemplate from "../ServicePageTemplate";
|
||||||
import { getPageById } from "../../wordpress";
|
import { getPageById } from "../../wordpress";
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ✅ Fonction pour supprimer les balises HTML des champs WYSIWYG
|
|
||||||
*/
|
|
||||||
const stripHtml = (html) => {
|
|
||||||
const doc = new DOMParser().parseFromString(html || "", "text/html");
|
|
||||||
return doc.body.textContent || "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const ServiceQuatre = () => {
|
const ServiceQuatre = () => {
|
||||||
const [acfData, setAcfData] = useState(null);
|
const [acfData, setAcfData] = useState(null);
|
||||||
const [seoData, setSeoData] = useState(null); // ✅ Stocke les données SEO de RankMath
|
const [seoData, setSeoData] = useState(null); // ✅ Stocke les données SEO de RankMath
|
||||||
@@ -58,36 +49,61 @@ const ServiceQuatre = () => {
|
|||||||
return <p>Chargement...</p>;
|
return <p>Chargement...</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStoredValue = (key) =>
|
||||||
|
typeof window !== "undefined" ? window.localStorage.getItem(key) : null;
|
||||||
|
|
||||||
|
const storedHeroBackground = getStoredValue(`hero_bg_${pageId}`);
|
||||||
|
const storedHeroImage = getStoredValue(`hero_img_${pageId}`);
|
||||||
|
|
||||||
|
const heroBackground =
|
||||||
|
acfData?.hero_background ||
|
||||||
|
acfData?.background_hero ||
|
||||||
|
acfData?.hero_bg ||
|
||||||
|
storedHeroBackground ||
|
||||||
|
"linear-gradient(135deg, #07132a 0%, #01040c 90%)";
|
||||||
|
|
||||||
|
const heroImage =
|
||||||
|
acfData?.hero_background_image ||
|
||||||
|
acfData?.hero_background_image_url ||
|
||||||
|
acfData?.hero_image_url ||
|
||||||
|
acfData?.hero_image ||
|
||||||
|
acfData?.image_hero ||
|
||||||
|
acfData?.img_hero_url ||
|
||||||
|
acfData?.img_hero ||
|
||||||
|
storedHeroImage ||
|
||||||
|
"https://preprod.octopusdesign.fr/api-octopus/server/wp-content/uploads/2025/02/service-securite-incendie.webp";
|
||||||
|
|
||||||
const serviceDetails = {
|
const serviceDetails = {
|
||||||
title: acfData?.titre_principal || "Titre héros !",
|
title: acfData?.titre_principal || "Titre héros !",
|
||||||
subtitle: stripHtml(acfData?.description_principal) || "Description, héros",
|
subtitle: acfData?.description_principal || "<p>Description, héros</p>",
|
||||||
|
|
||||||
image: acfData?.hero_image || "https://preprod.octopusdesign.fr/api-octopus/server/wp-content/uploads/2025/02/service-securite-incendie.webp",
|
image: heroImage,
|
||||||
|
heroBackground,
|
||||||
ctaText: "En savoir plus",
|
ctaText: "En savoir plus",
|
||||||
ctaLink: "/contact",
|
ctaLink: "/contact",
|
||||||
|
|
||||||
interestTitle: acfData?.second_titre || "Titre interet",
|
interestTitle: acfData?.second_titre || "Titre interet",
|
||||||
description: stripHtml(acfData?.second_description) || "Description interet",
|
description: acfData?.second_description || "<p>Description interet</p>",
|
||||||
|
|
||||||
desirTitle: "Titre",
|
desirTitle: "Titre",
|
||||||
|
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
title: acfData?.titre_carte_one || "Titre !",
|
title: acfData?.titre_carte_one || "Titre !",
|
||||||
description: stripHtml(acfData?.description_carte_one).substring(0, 100) + "...",
|
description: acfData?.description_carte_one || "",
|
||||||
modalText: stripHtml(acfData?.description_carte_one) || "Description Modal.",
|
modalText: acfData?.description_carte_one || "Description Modal.",
|
||||||
modalImage: "https://picsum.photos/id/300/800/400.webp",
|
modalImage: "https://picsum.photos/id/300/800/400.webp",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: acfData?.titre_carte_two || "Titre !",
|
title: acfData?.titre_carte_two || "Titre !",
|
||||||
description: stripHtml(acfData?.description_carte_two).substring(0, 100) + "...",
|
description: acfData?.description_carte_two || "",
|
||||||
modalText: stripHtml(acfData?.description_carte_two) || "Description Modal.",
|
modalText: acfData?.description_carte_two || "Description Modal.",
|
||||||
modalImage: "https://picsum.photos/id/301/800/400.webp",
|
modalImage: "https://picsum.photos/id/301/800/400.webp",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: acfData?.titre_carte_tree || "Titre !",
|
title: acfData?.titre_carte_tree || "Titre !",
|
||||||
description: stripHtml(acfData?.description_carte_tree).substring(0, 100) + "...",
|
description: acfData?.description_carte_tree || "",
|
||||||
modalText: stripHtml(acfData?.description_carte_tree) || "Description Modal.",
|
modalText: acfData?.description_carte_tree || "Description Modal.",
|
||||||
modalImage: "https://picsum.photos/id/302/800/400.webp",
|
modalImage: "https://picsum.photos/id/302/800/400.webp",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const ServiceTrois = () => {
|
|||||||
subtitle: "IN3 est un bureau d’études technique spécialisé dans l’ingénierie électrique du bâtiment.",
|
subtitle: "IN3 est un bureau d’études technique spécialisé dans l’ingénierie électrique du bâtiment.",
|
||||||
|
|
||||||
image: "https://picsum.photos/id/1019/1920/1080.webp",
|
image: "https://picsum.photos/id/1019/1920/1080.webp",
|
||||||
|
heroBackground: "linear-gradient(135deg,#0c101f 0%,#03050b 85%)",
|
||||||
ctaText: "En savoir plus",
|
ctaText: "En savoir plus",
|
||||||
ctaLink: "/contact",
|
ctaLink: "/contact",
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const ServiceUn = () => {
|
|||||||
"Notre bureau d’études in3 au Mans conçoit des bâtiments à construire ou à rénover selon le programme fourni par le maître de l’ouvrage, de diriger l’exécution des marchés de travaux, de proposer le règlement des travaux et leur réception. Nos missions sont les suivantes :", // ✅ Modifiable individuellement
|
"Notre bureau d’études in3 au Mans conçoit des bâtiments à construire ou à rénover selon le programme fourni par le maître de l’ouvrage, de diriger l’exécution des marchés de travaux, de proposer le règlement des travaux et leur réception. Nos missions sont les suivantes :", // ✅ Modifiable individuellement
|
||||||
|
|
||||||
image: "https://picsum.photos/id/1026/1920/1080.webp",
|
image: "https://picsum.photos/id/1026/1920/1080.webp",
|
||||||
|
heroBackground: "linear-gradient(135deg,#0b1428 0%,#16223f 40%,#04070f 100%)",
|
||||||
ctaText: "En savoir plus",
|
ctaText: "En savoir plus",
|
||||||
ctaLink: "/contact",
|
ctaLink: "/contact",
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState, useMemo, isValidElement, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -18,6 +18,13 @@ import "swiper/css/navigation";
|
|||||||
import "swiper/css/pagination";
|
import "swiper/css/pagination";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
|
import {
|
||||||
|
extractFirstImageSrc,
|
||||||
|
removeImageTags,
|
||||||
|
stripHtml,
|
||||||
|
} from "../utils/htmlHelpers";
|
||||||
|
|
||||||
|
const DEFAULT_CARD_IMAGE = "https://picsum.photos/id/43/800/400.webp";
|
||||||
|
|
||||||
const ServicePageTemplate = ({
|
const ServicePageTemplate = ({
|
||||||
title,
|
title,
|
||||||
@@ -27,6 +34,7 @@ const ServicePageTemplate = ({
|
|||||||
desirTitle,
|
desirTitle,
|
||||||
features,
|
features,
|
||||||
image,
|
image,
|
||||||
|
heroBackground,
|
||||||
ctaText,
|
ctaText,
|
||||||
ctaLink,
|
ctaLink,
|
||||||
carouselItems,
|
carouselItems,
|
||||||
@@ -35,13 +43,88 @@ const ServicePageTemplate = ({
|
|||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const [modalContent, setModalContent] = useState({});
|
const [modalContent, setModalContent] = useState({});
|
||||||
const [loadingImage, setLoadingImage] = useState(true);
|
const [loadingImage, setLoadingImage] = useState(true);
|
||||||
|
const [heroLoaded, setHeroLoaded] = useState(false);
|
||||||
|
|
||||||
|
const renderRichText = (value) => {
|
||||||
|
if (value === null || value === undefined) return null;
|
||||||
|
if (isValidElement(value)) return value;
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return <span dangerouslySetInnerHTML={{ __html: value }} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const descriptionIsElement = isValidElement(description);
|
||||||
|
const rawDescriptionHtml =
|
||||||
|
!descriptionIsElement && typeof description === "string" ? description : "";
|
||||||
|
|
||||||
|
const descriptionMedia = useMemo(() => {
|
||||||
|
if (!rawDescriptionHtml) {
|
||||||
|
return { html: rawDescriptionHtml, media: null };
|
||||||
|
}
|
||||||
|
if (typeof window === "undefined" || !window.DOMParser) {
|
||||||
|
return { html: rawDescriptionHtml, media: null };
|
||||||
|
}
|
||||||
|
const parser = new window.DOMParser();
|
||||||
|
const doc = parser.parseFromString(rawDescriptionHtml, "text/html");
|
||||||
|
const firstImage = doc.body.querySelector("img");
|
||||||
|
if (firstImage) {
|
||||||
|
const media = {
|
||||||
|
src: firstImage.getAttribute("src"),
|
||||||
|
alt: firstImage.getAttribute("alt") || "",
|
||||||
|
};
|
||||||
|
firstImage.remove();
|
||||||
|
return { html: doc.body.innerHTML, media };
|
||||||
|
}
|
||||||
|
return { html: rawDescriptionHtml, media: null };
|
||||||
|
}, [rawDescriptionHtml]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!image) {
|
||||||
|
setHeroLoaded(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setHeroLoaded(false);
|
||||||
|
const img = new Image();
|
||||||
|
img.src = image;
|
||||||
|
img.onload = () => setHeroLoaded(true);
|
||||||
|
img.onerror = () => setHeroLoaded(true);
|
||||||
|
return () => {
|
||||||
|
img.onload = null;
|
||||||
|
img.onerror = null;
|
||||||
|
};
|
||||||
|
}, [image]);
|
||||||
|
|
||||||
|
const defaultGradient = "linear-gradient(135deg, #0a1930 0%, #020710 85%)";
|
||||||
|
const activeGradient = heroBackground || defaultGradient;
|
||||||
|
const heroBackgroundStyle =
|
||||||
|
heroLoaded && image
|
||||||
|
? `url(${image}), ${activeGradient}`
|
||||||
|
: activeGradient;
|
||||||
|
|
||||||
|
const buildModalPayload = (feature) => {
|
||||||
|
const rawHtml = feature.modalText || feature.description || "";
|
||||||
|
const sanitizedHtml = removeImageTags(rawHtml);
|
||||||
|
const extractedImage = extractFirstImageSrc(rawHtml);
|
||||||
|
const dynamicImage =
|
||||||
|
extractedImage || feature.modalImage || DEFAULT_CARD_IMAGE;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: feature.title,
|
||||||
|
text: sanitizedHtml || "Texte non disponible.",
|
||||||
|
image: dynamicImage,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPreviewText = (feature) => {
|
||||||
|
const rawPreview = feature.description || feature.modalText || "";
|
||||||
|
const textOnly = stripHtml(removeImageTags(rawPreview));
|
||||||
|
if (!textOnly) return "Contenu indisponible.";
|
||||||
|
return textOnly.length > 140 ? `${textOnly.substring(0, 140)}…` : textOnly;
|
||||||
|
};
|
||||||
|
|
||||||
const handleCardClick = (feature) => {
|
const handleCardClick = (feature) => {
|
||||||
setModalContent({
|
setModalContent(buildModalPayload(feature));
|
||||||
title: feature.title,
|
|
||||||
text: feature.modalText || "Texte non disponible.",
|
|
||||||
image: feature.modalImage || "https://picsum.photos/id/43/800/400.webp",
|
|
||||||
});
|
|
||||||
setLoadingImage(true);
|
setLoadingImage(true);
|
||||||
setOpenModal(true);
|
setOpenModal(true);
|
||||||
};
|
};
|
||||||
@@ -77,9 +160,9 @@ const ServicePageTemplate = ({
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
mt: 5,
|
mt: 5,
|
||||||
backgroundImage: `url(${image})`,
|
backgroundImage: heroBackgroundStyle,
|
||||||
backgroundSize: "cover",
|
backgroundSize: heroLoaded && image ? "cover, cover" : "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: heroLoaded && image ? "center, center" : "center",
|
||||||
minHeight: "70vh",
|
minHeight: "70vh",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -87,6 +170,8 @@ const ServicePageTemplate = ({
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
color: "black",
|
color: "black",
|
||||||
padding: "20px",
|
padding: "20px",
|
||||||
|
backgroundColor: heroLoaded ? undefined : "#020712",
|
||||||
|
transition: "background-image 0.8s ease, opacity 0.4s ease",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@@ -112,8 +197,8 @@ const ServicePageTemplate = ({
|
|||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ mb: 4 }}>
|
<Typography variant="body1" component="div" sx={{ mb: 4 }}>
|
||||||
{subtitle}
|
{renderRichText(subtitle) ?? subtitle ?? ""}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
href={ctaLink}
|
href={ctaLink}
|
||||||
@@ -133,29 +218,69 @@ const ServicePageTemplate = ({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Section numero 1 */}
|
{/* Section numero 1 */}
|
||||||
<Box sx={{ padding: "40px 20px", textAlign: "center" }}>
|
<Box sx={{ fontWeight: "5", textAlign: "center" }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h2"
|
variant="h2"
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
mb: 2,
|
mb: 7,
|
||||||
fontSize: { xs: "2rem", md: "2rem", lg: "2rem" },
|
fontSize: { xs: "2rem", md: "2rem", lg: "2rem" },
|
||||||
whiteSpace: "pre-line",
|
whiteSpace: "pre-line",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{interestTitle}
|
{interestTitle}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
{descriptionIsElement ? (
|
||||||
variant="body1"
|
<Typography variant="body1" component="div">
|
||||||
|
{renderRichText(description)}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: "1000px",
|
display: "flex",
|
||||||
margin: "0 auto",
|
flexDirection: { xs: "column", md: "row" },
|
||||||
mb: 4,
|
gap: 4,
|
||||||
whiteSpace: "pre-line",
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: { xs: "center", md: "left" },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{description}
|
{descriptionMedia.media?.src && (
|
||||||
</Typography>
|
<Box
|
||||||
|
component="img"
|
||||||
|
src={descriptionMedia.media.src}
|
||||||
|
alt={descriptionMedia.media.alt || "Illustration"}
|
||||||
|
sx={{
|
||||||
|
width: { xs: "100%", md: "42%" },
|
||||||
|
borderRadius: 4,
|
||||||
|
objectFit: "cover",
|
||||||
|
boxShadow: "0 25px 45px rgba(0,0,0,0.25)",
|
||||||
|
mb: { xs: 2, md: 5 },
|
||||||
|
mx: { xs: "auto", md: 0 },
|
||||||
|
marginLeft: { xs: 2, md: 6 } ,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
component="div"
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
textAlign: { xs: "center", md: "left" },
|
||||||
|
"& p": {
|
||||||
|
margin: "0 auto",
|
||||||
|
textAlign: "left",
|
||||||
|
lineHeight: 1.65,
|
||||||
|
maxWidth: 760,
|
||||||
|
},
|
||||||
|
"& p + p": {
|
||||||
|
marginTop: 1.2,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
dangerouslySetInnerHTML={{ __html: descriptionMedia.html || "" }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Section numero 2 */}
|
{/* Section numero 2 */}
|
||||||
@@ -198,16 +323,9 @@ const ServicePageTemplate = ({
|
|||||||
>
|
>
|
||||||
{feature.title}
|
{feature.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography variant="body2" sx={{ color: "#555" }}>
|
||||||
variant="body2"
|
{getPreviewText(feature)}
|
||||||
sx={{ color: "#555" }}
|
</Typography>
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html:
|
|
||||||
feature.description.length > 100
|
|
||||||
? `${feature.description.substring(0, 100)}...`
|
|
||||||
: feature.description,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -352,7 +470,7 @@ ServicePageTemplate.propTypes = {
|
|||||||
modalImage: PropTypes.string,
|
modalImage: PropTypes.string,
|
||||||
})
|
})
|
||||||
).isRequired,
|
).isRequired,
|
||||||
image: PropTypes.string.isRequired,
|
image: PropTypes.string,
|
||||||
ctaText: PropTypes.string.isRequired,
|
ctaText: PropTypes.string.isRequired,
|
||||||
ctaLink: PropTypes.string.isRequired,
|
ctaLink: PropTypes.string.isRequired,
|
||||||
carouselItems: PropTypes.arrayOf(
|
carouselItems: PropTypes.arrayOf(
|
||||||
@@ -362,12 +480,14 @@ ServicePageTemplate.propTypes = {
|
|||||||
image: PropTypes.string.isRequired,
|
image: PropTypes.string.isRequired,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
heroBackground: PropTypes.string,
|
||||||
seo: PropTypes.object, // ✅ Ajout du SEO en option
|
seo: PropTypes.object, // ✅ Ajout du SEO en option
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ Valeurs par défaut
|
// ✅ Valeurs par défaut
|
||||||
ServicePageTemplate.defaultProps = {
|
ServicePageTemplate.defaultProps = {
|
||||||
carouselItems: [],
|
carouselItems: [],
|
||||||
|
heroBackground: "linear-gradient(135deg, #0a1930 0%, #020710 85%)",
|
||||||
seo: {}, // ✅ SEO par défaut vide
|
seo: {}, // ✅ SEO par défaut vide
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function YourApp() {
|
|||||||
element={<ServiceUn />}
|
element={<ServiceUn />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/services/structure-beton-charpente-metallique-bois"
|
path="/services/formations-web"
|
||||||
element={<ServiceDeux />}
|
element={<ServiceDeux />}
|
||||||
/>
|
/>
|
||||||
<Route path="/services/electricite" element={<ServiceTrois />} />
|
<Route path="/services/electricite" element={<ServiceTrois />} />
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
const hasDomParser =
|
||||||
|
(typeof window !== "undefined" && typeof window.DOMParser !== "undefined") ||
|
||||||
|
typeof DOMParser !== "undefined";
|
||||||
|
|
||||||
|
const createDocument = (html) => {
|
||||||
|
if (!hasDomParser || !html) return null;
|
||||||
|
const ParserConstructor =
|
||||||
|
typeof DOMParser !== "undefined"
|
||||||
|
? DOMParser
|
||||||
|
: typeof window !== "undefined" && window.DOMParser
|
||||||
|
? window.DOMParser
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!ParserConstructor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = new ParserConstructor();
|
||||||
|
return parser.parseFromString(html, "text/html");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractFirstImageSrc = (html) => {
|
||||||
|
if (!html) return "";
|
||||||
|
const doc = createDocument(html);
|
||||||
|
if (doc) {
|
||||||
|
const img = doc.querySelector("img");
|
||||||
|
if (img) {
|
||||||
|
return img.getAttribute("src") || "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = html.match(/<img[^>]+src=["']([^"']+)["']/i);
|
||||||
|
return match?.[1] || "";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeImageTags = (html) => {
|
||||||
|
if (!html) return "";
|
||||||
|
return html.replace(/<img\b[^>]*>/gi, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stripHtml = (html) => {
|
||||||
|
if (!html) return "";
|
||||||
|
const doc = createDocument(html);
|
||||||
|
if (doc) {
|
||||||
|
return doc.body.textContent || "";
|
||||||
|
}
|
||||||
|
return html.replace(/<[^>]*>/g, "");
|
||||||
|
};
|
||||||
@@ -267,7 +267,7 @@ export async function getPageById(pageId) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`https://preprod.octopusdesign.fr/api-octopus/server/wp-json/wp/v2/pages/${pageId}?_fields=id,title,acf,_rank_math_seo_meta`
|
`https://preprod.octopusdesign.fr/api-octopus/server/wp-json/wp/v2/pages/${pageId}?_fields=id,title,link,acf,_rank_math_seo_meta`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user