import { useState, useMemo, isValidElement, useEffect } from "react"; import { Box, Typography, Button, Grid, Card, CardContent, Modal, Fade, Backdrop, CircularProgress, } from "@mui/material"; import { Swiper, SwiperSlide } from "swiper/react"; import { Navigation, Pagination, Autoplay } from "swiper/modules"; import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; import PropTypes from "prop-types"; 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 = ({ title, subtitle, interestTitle, description, desirTitle, features, image, heroBackground, ctaText, ctaLink, carouselItems, seo, // ✅ Ajout de l'objet SEO }) => { const [openModal, setOpenModal] = useState(false); const [modalContent, setModalContent] = useState({}); 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 ; } 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) => { setModalContent(buildModalPayload(feature)); setLoadingImage(true); setOpenModal(true); }; const handleCloseModal = () => { setOpenModal(false); }; return ( {/* ✅ SEO META TAGS */} {seo?.metaTitle || title} {/* Section Hero */} {title} {renderRichText(subtitle) ?? subtitle ?? ""} {/* Section numero 1 */} {interestTitle} {descriptionIsElement ? ( {renderRichText(description)} ) : ( {descriptionMedia.media?.src && ( )} )} {/* Section numero 2 */} {desirTitle} {features.map((feature, index) => ( handleCardClick(feature)} sx={{ cursor: "pointer", textAlign: "center", padding: "20px", backgroundColor: "#f9f9f9", transition: "0.3s", "&:hover": { transform: "scale(1.05)", boxShadow: 5 }, }} > {feature.title} {getPreviewText(feature)} ))} {/* Modal */} {loadingImage && } {modalContent.title} setLoadingImage(false)} /> {modalContent.title} {/* Carousel Section */} {carouselItems && carouselItems.length > 0 && ( Découvrez nos réalisations {carouselItems.map((item, index) => ( {item.title} {item.title} {item.description} ))} )} ); }; // ✅ Définition des types des props avec PropTypes ServicePageTemplate.propTypes = { title: PropTypes.string.isRequired, subtitle: PropTypes.string.isRequired, interestTitle: PropTypes.string.isRequired, description: PropTypes.string.isRequired, desirTitle: PropTypes.string.isRequired, features: PropTypes.arrayOf( PropTypes.shape({ title: PropTypes.string.isRequired, description: PropTypes.string.isRequired, modalText: PropTypes.string, modalImage: PropTypes.string, }) ).isRequired, image: PropTypes.string, ctaText: PropTypes.string.isRequired, ctaLink: PropTypes.string.isRequired, carouselItems: PropTypes.arrayOf( PropTypes.shape({ title: PropTypes.string.isRequired, description: PropTypes.string.isRequired, image: PropTypes.string.isRequired, }) ), heroBackground: PropTypes.string, seo: PropTypes.object, // ✅ Ajout du SEO en option }; // ✅ Valeurs par défaut ServicePageTemplate.defaultProps = { carouselItems: [], heroBackground: "linear-gradient(135deg, #0a1930 0%, #020710 85%)", seo: {}, // ✅ SEO par défaut vide }; export default ServicePageTemplate;