Maj Api and create contact page
This commit is contained in:
parent
8fcd728887
commit
037cffaa0a
@ -2,6 +2,7 @@ import axios from 'axios';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: 'https://preprod.octopusdesign.fr/api-octopus/server/wp-json',
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
export default api;
|
||||
@ -124,19 +124,6 @@ const Header = () => {
|
||||
|
||||
{/* Navigation Links */}
|
||||
<Box sx={{ display: { xs: "none", md: "flex" }, gap: 2 }}>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/posts"
|
||||
color="inherit"
|
||||
sx={{
|
||||
fontWeight: location.pathname === "/posts" ? "bold" : "normal",
|
||||
textDecoration:
|
||||
location.pathname === "/posts" ? "underline" : "none",
|
||||
...commonButtonStyles,
|
||||
}}
|
||||
>
|
||||
Articles
|
||||
</Button>
|
||||
|
||||
{/* Services Dropdown */}
|
||||
<Button
|
||||
@ -148,11 +135,11 @@ const Header = () => {
|
||||
fontWeight: location.pathname.includes("/services")
|
||||
? "bold"
|
||||
: "normal",
|
||||
textDecoration: location.pathname.includes("/services")
|
||||
textDecoration: location.pathname.includes("/services")
|
||||
? "underline"
|
||||
: "none",
|
||||
...commonButtonStyles,
|
||||
}}
|
||||
...commonButtonStyles,
|
||||
}}
|
||||
>
|
||||
Services
|
||||
</Button>
|
||||
@ -191,6 +178,19 @@ const Header = () => {
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<Button
|
||||
component={Link}
|
||||
to="/posts"
|
||||
color="inherit"
|
||||
sx={{
|
||||
fontWeight: location.pathname === "/posts" ? "bold" : "normal",
|
||||
textDecoration:
|
||||
location.pathname === "/posts" ? "underline" : "none",
|
||||
...commonButtonStyles,
|
||||
}}
|
||||
>
|
||||
Articles
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/about"
|
||||
@ -300,15 +300,6 @@ const Header = () => {
|
||||
<HomeIcon sx={{ marginRight: 1 }} />
|
||||
<ListItemText primary="Accueil" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
component={Link}
|
||||
to="/posts"
|
||||
selected={location.pathname === "/posts"}
|
||||
>
|
||||
<ArticleIcon sx={{ marginRight: 1 }} />
|
||||
<ListItemText primary="Articles" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
component={Link}
|
||||
@ -336,6 +327,15 @@ const Header = () => {
|
||||
<WorkIcon sx={{ marginRight: 1 }} />
|
||||
<ListItemText primary="Formation Video" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
component={Link}
|
||||
to="/posts"
|
||||
selected={location.pathname === "/posts"}
|
||||
>
|
||||
<ArticleIcon sx={{ marginRight: 1 }} />
|
||||
<ListItemText primary="Articles" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
component={Link}
|
||||
|
||||
@ -1,40 +1,471 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import SEO from '../SEO';
|
||||
import api from '../../api';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
TextField,
|
||||
Typography,
|
||||
Button,
|
||||
Grid,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Snackbar,
|
||||
Alert,
|
||||
CircularProgress,
|
||||
Card,
|
||||
CardContent,
|
||||
Modal,
|
||||
Fade,
|
||||
Backdrop,
|
||||
} from "@mui/material";
|
||||
import SEO from "../SEO";
|
||||
import api from "../../api";
|
||||
|
||||
const Contact = () => {
|
||||
// États pour le SEO
|
||||
const [metaTitle, setMetaTitle] = useState('Titre par défaut');
|
||||
const [metaDescription, setMetaDescription] = useState('Description par défaut.');
|
||||
const [metaTitle, setMetaTitle] = useState("Contactez-nous");
|
||||
const [metaDescription, setMetaDescription] = useState(
|
||||
"Contactez notre équipe pour plus d'informations."
|
||||
);
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
subject: "",
|
||||
message: "",
|
||||
consent: false,
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [successMessage, setSuccessMessage] = useState(false);
|
||||
const [openLightbox, setOpenLightbox] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPageData = async () => {
|
||||
try {
|
||||
// Appel à l'API pour récupérer les données
|
||||
const response = await api.get('wp/v2/pages/116?_fields=acf,rank_math_title,rank_math_description');
|
||||
const { rank_math_title, rank_math_description } = response.data;
|
||||
useEffect(() => {
|
||||
const fetchSEOData = async () => {
|
||||
try {
|
||||
const response = await api.get(
|
||||
"wp/v2/pages/116?_fields=acf,rank_math_title,rank_math_description"
|
||||
);
|
||||
const { rank_math_title, rank_math_description } = response.data;
|
||||
setMetaTitle(rank_math_title || "Contactez-nous");
|
||||
setMetaDescription(rank_math_description || "Contactez notre équipe.");
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération des données SEO :", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Mise à jour des métadonnées SEO
|
||||
setMetaTitle(rank_math_title || 'Titre par défaut');
|
||||
setMetaDescription(rank_math_description || 'Description par défaut.');
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des données :', error);
|
||||
fetchSEOData();
|
||||
}, []);
|
||||
|
||||
const validate = () => {
|
||||
const newErrors = {};
|
||||
if (!formData.name) newErrors.name = "Le nom est requis.";
|
||||
if (!formData.email || !/\S+@\S+\.\S+/.test(formData.email))
|
||||
newErrors.email = "Une adresse e-mail valide est requise.";
|
||||
if (!formData.subject) newErrors.subject = "Le sujet est requis.";
|
||||
if (!formData.message) newErrors.message = "Le message est requis.";
|
||||
if (!formData.consent)
|
||||
newErrors.consent = "Vous devez accepter la politique de confidentialité.";
|
||||
return newErrors;
|
||||
};
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setFormData({
|
||||
...formData,
|
||||
[name]: type === "checkbox" ? checked : value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const validationErrors = validate();
|
||||
setErrors(validationErrors);
|
||||
|
||||
if (Object.keys(validationErrors).length === 0) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://votre-site.com/wp-json/custom/v1/contact",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
}
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
setSuccessMessage(true);
|
||||
setFormData({
|
||||
name: "",
|
||||
email: "",
|
||||
subject: "",
|
||||
message: "",
|
||||
consent: false,
|
||||
});
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.message || "Une erreur est survenue.");
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenLightbox = () => {
|
||||
setOpenLightbox(true);
|
||||
};
|
||||
|
||||
const handleCloseLightbox = () => {
|
||||
setOpenLightbox(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* SEO */}
|
||||
<SEO title={metaTitle} description={metaDescription} />
|
||||
|
||||
{/* Section Attention */}
|
||||
<Box
|
||||
sx={{
|
||||
backgroundImage: `url('https://via.placeholder.com/1920x800')`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
minHeight: "50vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
textAlign: "center",
|
||||
color: "white",
|
||||
padding: "20px",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="h2"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: { xs: "2rem", md: "3rem", lg: "4rem" },
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
Contactez-nous
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontSize: { xs: "1rem", md: "1.5rem" },
|
||||
maxWidth: "600px",
|
||||
mx: "auto",
|
||||
}}
|
||||
>
|
||||
Une question ? Une demande particulière ? Remplissez le formulaire
|
||||
ci-dessous ou consultez nos coordonnées.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Section Coordonnées et Horaires */}
|
||||
<Box sx={{ padding: "40px 20px", backgroundColor: "#f9f9f9" }}>
|
||||
<Grid container spacing={4}>
|
||||
{/* Coordonnées */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card
|
||||
sx={{
|
||||
boxShadow: 2,
|
||||
borderRadius: 2,
|
||||
padding: "20px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
height: "80%",
|
||||
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
color: "#0e467f",
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
Nos coordonnées
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<strong>Adresse :</strong> 123 Rue Exemple, Paris, France
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<strong>Téléphone :</strong> +33 1 23 45 67 89
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<strong>Email :</strong> contact@exemple.com
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
onClick={handleOpenLightbox}
|
||||
sx={{
|
||||
position: "relative",
|
||||
cursor: "pointer",
|
||||
width: "120px",
|
||||
height: "120px",
|
||||
borderRadius: "8px",
|
||||
overflow: "hidden",
|
||||
boxShadow: 3,
|
||||
"&:hover": {
|
||||
transform: "scale(1.10)",
|
||||
boxShadow: 4,
|
||||
transition: "transform 0.3s, box-shadow 0.3s",
|
||||
},
|
||||
"&:hover::before": {
|
||||
content: '"Cliquez pour agrandir"',
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
||||
color: "white",
|
||||
padding: "5px 10px",
|
||||
borderRadius: "4px",
|
||||
fontSize: "12px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="https://picsum.photos/id/442/300.webp"
|
||||
alt="Carte"
|
||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||
|
||||
/>
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Horaires */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card
|
||||
sx={{
|
||||
boxShadow: 2,
|
||||
borderRadius: 2,
|
||||
padding: "20px",
|
||||
backgroundColor: "#fff",
|
||||
height: "80%",
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
color: "#0e467f",
|
||||
mb: 2,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Horaires d'ouverture
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<strong>Lundi - Vendredi :</strong> 9h00 - 18h00
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<strong>Samedi :</strong> 10h00 - 14h00
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<strong>Dimanche :</strong> Fermé
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{/* Lightbox */}
|
||||
<Modal
|
||||
open={openLightbox}
|
||||
onClose={handleCloseLightbox}
|
||||
closeAfterTransition
|
||||
BackdropComponent={Backdrop}
|
||||
BackdropProps={{ timeout: 500 }}
|
||||
>
|
||||
<Fade in={openLightbox}>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: "90%",
|
||||
maxWidth: "800px",
|
||||
backgroundColor: "white",
|
||||
boxShadow: 24,
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2624.9998319324044!2d2.2922928156738935!3d48.85884407928788!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x47e66fdf78ccf6fd%3A0x10793db0b6d65f9b!2sEiffel+Tower!5e0!3m2!1sen!2sfr!4v1618237600000!5m2!1sen!2sfr"
|
||||
width="100%"
|
||||
height="400"
|
||||
title="Carte interactive"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen=""
|
||||
loading="lazy"
|
||||
/>
|
||||
</Box>
|
||||
</Fade>
|
||||
</Modal>
|
||||
|
||||
{/* Section Action */}
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#0e467f",
|
||||
color: "white",
|
||||
textAlign: "center",
|
||||
padding: "40px 20px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
mb: 2,
|
||||
fontSize: { xs: "1.5rem", md: "2rem" },
|
||||
}}
|
||||
>
|
||||
Besoin d'une assistance immédiate ?
|
||||
</Typography>
|
||||
<Button
|
||||
href="/aide"
|
||||
variant="contained"
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
fontWeight: "bold",
|
||||
backgroundColor: "#00bcd4",
|
||||
"&:hover": { backgroundColor: "#0288d1" },
|
||||
}}
|
||||
>
|
||||
Obtenez de l'aide maintenant
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Formulaire de contact */}
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "800px",
|
||||
margin: "auto",
|
||||
padding: "40px",
|
||||
boxShadow: 3,
|
||||
borderRadius: 2,
|
||||
backgroundColor: "#fff",
|
||||
mt: 4,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{ mb: 4, textAlign: "center", fontWeight: "bold", color: "#0e467f" }}
|
||||
>
|
||||
Formulaire de contact
|
||||
</Typography>
|
||||
<form onSubmit={handleSubmit} noValidate>
|
||||
<TextField
|
||||
label="Nom"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
error={Boolean(errors.name)}
|
||||
helperText={errors.name}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
label="E-mail"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
error={Boolean(errors.email)}
|
||||
helperText={errors.email}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
label="Sujet"
|
||||
name="subject"
|
||||
value={formData.subject}
|
||||
onChange={handleChange}
|
||||
error={Boolean(errors.subject)}
|
||||
helperText={errors.subject}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
label="Message"
|
||||
name="message"
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
error={Boolean(errors.message)}
|
||||
helperText={errors.message}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline
|
||||
rows={4}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
name="consent"
|
||||
checked={formData.consent}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
}
|
||||
};
|
||||
label="J'accepte la politique de confidentialité."
|
||||
sx={{ mt: 2, color: errors.consent ? "red" : "inherit" }}
|
||||
/>
|
||||
{errors.consent && (
|
||||
<Typography variant="caption" color="error">
|
||||
{errors.consent}
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ textAlign: "center", mt: 3 }}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
disabled={loading}
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
fontWeight: "bold",
|
||||
px: 5,
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<CircularProgress size={24} sx={{ color: "#fff" }} />
|
||||
) : (
|
||||
"Envoyer"
|
||||
)}
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
|
||||
fetchPageData();
|
||||
}, []); // Exécution au premier rendu
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* SEO */}
|
||||
<SEO title={metaTitle} description={metaDescription} />
|
||||
|
||||
{/* Contenu de la page */}
|
||||
<h1>Contact</h1>
|
||||
<p>Bienvenue sur la page Contact.</p>
|
||||
</div>
|
||||
);
|
||||
<Snackbar
|
||||
open={successMessage}
|
||||
autoHideDuration={6000}
|
||||
onClose={() => setSuccessMessage(false)}
|
||||
>
|
||||
<Alert onClose={() => setSuccessMessage(false)} severity="success">
|
||||
Message envoyé avec succès !
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
@ -15,7 +15,6 @@ import { Navigation, Pagination, Autoplay } from "swiper/modules";
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
import { useIsTouchDevice } from "./hooks/useIsTouchDevice"; // Import du hook
|
||||
|
||||
const ServicePageTemplate = ({
|
||||
title,
|
||||
@ -29,7 +28,6 @@ const ServicePageTemplate = ({
|
||||
}) => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [modalContent, setModalContent] = useState({});
|
||||
const isTouchDevice = useIsTouchDevice(); // Détection des écrans tactiles
|
||||
|
||||
const handleCardClick = (feature) => {
|
||||
setModalContent({
|
||||
@ -68,18 +66,25 @@ const ServicePageTemplate = ({
|
||||
}}
|
||||
>
|
||||
<Box sx={{ maxWidth: "800px" }}>
|
||||
<Typography
|
||||
variant="h1"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
mb: 2,
|
||||
fontSize: { xs: "2rem", md: "3rem", lg: "4rem" },
|
||||
<Typography
|
||||
variant="h1"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
mb: 2,
|
||||
fontSize: { xs: '2rem', md: '3rem', lg: '4rem' }, // Responsive
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ mb: 4 }}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
mb: 4
|
||||
}}
|
||||
>
|
||||
{subtitle}
|
||||
|
||||
</Typography>
|
||||
<Button
|
||||
href={ctaLink}
|
||||
@ -100,16 +105,16 @@ const ServicePageTemplate = ({
|
||||
|
||||
{/* Section Interest */}
|
||||
<Box sx={{ padding: "40px 20px", textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h2"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
<Typography variant="h2"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
mb: 2,
|
||||
fontSize: { xs: "2rem", md: "3rem", lg: "3rem" },
|
||||
fontSize: { xs: '2rem', md: '3rem', lg: '3rem' }, // Responsive
|
||||
}}
|
||||
>
|
||||
Pourquoi choisir ce service ?
|
||||
Pourquoi choisir notre formation ?
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ maxWidth: "800px", margin: "0 auto", mb: 4 }}
|
||||
@ -122,15 +127,17 @@ const ServicePageTemplate = ({
|
||||
<Box sx={{ backgroundColor: "#ffffff", padding: "40px 20px" }}>
|
||||
<Typography
|
||||
variant="h2"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
textAlign: "center",
|
||||
mb: 4,
|
||||
fontSize: { xs: "2rem", md: "3rem", lg: "3rem" },
|
||||
}}
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
textAlign: "center",
|
||||
mb: 4,
|
||||
fontSize: { xs: '2rem', md: '3rem', lg: '3rem' }, // Responsive
|
||||
|
||||
}}
|
||||
>
|
||||
Ce que nous offrons
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={4} justifyContent="center">
|
||||
{features.map((feature, index) => (
|
||||
<Grid item xs={12} md={4} key={index}>
|
||||
@ -144,11 +151,17 @@ const ServicePageTemplate = ({
|
||||
cursor: "pointer",
|
||||
transition: "transform 0.3s ease, box-shadow 0.3s ease",
|
||||
"&:hover": {
|
||||
transform: isTouchDevice ? "none" : "scale(1.05)",
|
||||
boxShadow: isTouchDevice ? 2 : 5,
|
||||
transform: {
|
||||
xs: "none", // Pas d'hover sur mobile
|
||||
md: "scale(1.05)", // Hover sur desktop uniquement
|
||||
},
|
||||
boxShadow: {
|
||||
xs: 2, // Pas d'effet de hover sur mobile
|
||||
md: 5, // Augmentation de l'ombre sur desktop
|
||||
},
|
||||
},
|
||||
"&:active": {
|
||||
transform: "scale(0.98)",
|
||||
transform: "scale(0.98)", // Effet de clic pour le tactile
|
||||
boxShadow: 3,
|
||||
},
|
||||
}}
|
||||
@ -238,8 +251,115 @@ const ServicePageTemplate = ({
|
||||
</Box>
|
||||
</Fade>
|
||||
</Modal>
|
||||
|
||||
{/* Carousel Section */}
|
||||
{carouselItems && carouselItems.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
padding: "40px 20px",
|
||||
textAlign: "center",
|
||||
backgroundColor: "#f9f9f9",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h2"
|
||||
sx={{
|
||||
marginBottom: 4,
|
||||
fontSize: { xs: '2rem', md: '3rem', lg: '3rem' }, // Responsive
|
||||
|
||||
}}
|
||||
>
|
||||
Découvrez nos réalisations
|
||||
</Typography>
|
||||
|
||||
<Swiper
|
||||
modules={[Navigation, Pagination, Autoplay]}
|
||||
spaceBetween={20}
|
||||
slidesPerView={1}
|
||||
navigation
|
||||
pagination={{ clickable: true }}
|
||||
autoplay={{ delay: 3000 }}
|
||||
loop
|
||||
>
|
||||
{carouselItems.map((item, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<Card
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "20px",
|
||||
boxShadow: 3,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={item.image}
|
||||
alt={item.title}
|
||||
style={{
|
||||
width: "40%",
|
||||
height: "auto",
|
||||
objectFit: "cover",
|
||||
borderRadius: "8px",
|
||||
marginRight: "20px",
|
||||
}}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography variant="h3"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: { xs: '1.2rem', md: '2rem', lg: '2.5rem' }, // Responsive
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography variant="body2">{item.description}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Section Action */}
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#0e467f",
|
||||
color: "white",
|
||||
textAlign: "center",
|
||||
padding: "40px 20px",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h2"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
mb: 2,
|
||||
fontSize: { xs: '2rem', md: '3rem', lg: '3rem' }, // Responsive
|
||||
|
||||
}}
|
||||
>
|
||||
Prêt à démarrer ?
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body1" sx={{ mb: 4 }}>
|
||||
Rejoignez-nous dès aujourd’hui et profitez de nos services pour
|
||||
booster votre activité.
|
||||
</Typography>
|
||||
<Button
|
||||
href={ctaLink}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
size="large"
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
fontWeight: "bold",
|
||||
backgroundColor: "#00bcd4",
|
||||
"&:hover": { backgroundColor: "#0288d1" },
|
||||
}}
|
||||
>
|
||||
{ctaText}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicePageTemplate;
|
||||
export default ServicePageTemplate;
|
||||
|
||||
16
server/.htaccess
Normal file
16
server/.htaccess
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
# BEGIN WordPress
|
||||
# Les directives (lignes) entre « BEGIN WordPress » et « END WordPress » sont générées
|
||||
# dynamiquement, et doivent être modifiées uniquement via les filtres WordPress.
|
||||
# Toute modification des directives situées entre ces marqueurs sera surchargée.
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
RewriteBase /api-octopus/server/
|
||||
RewriteRule ^index\.php$ - [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule . /api-octopus/server/index.php [L]
|
||||
</IfModule>
|
||||
|
||||
# END WordPress
|
||||
5
server/wp-content/themes/.htaccess
Normal file
5
server/wp-content/themes/.htaccess
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
# BEGIN WebP Express
|
||||
# Plugin is deactivated
|
||||
# END WebP Express
|
||||
|
||||
340
server/wp-content/themes/hello-elementor/functions.php
Normal file
340
server/wp-content/themes/hello-elementor/functions.php
Normal file
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
/**
|
||||
* Theme functions and definitions
|
||||
*
|
||||
* @package HelloElementor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
define( 'HELLO_ELEMENTOR_VERSION', '3.2.1' );
|
||||
|
||||
|
||||
if ( ! isset( $content_width ) ) {
|
||||
$content_width = 800; // Pixels.
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'hello_elementor_setup' ) ) {
|
||||
/**
|
||||
* Set up theme support.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function hello_elementor_setup() {
|
||||
if ( is_admin() ) {
|
||||
hello_maybe_update_theme_version_in_db();
|
||||
}
|
||||
|
||||
if ( apply_filters( 'hello_elementor_register_menus', true ) ) {
|
||||
register_nav_menus( [ 'menu-1' => esc_html__( 'Header', 'hello-elementor' ) ] );
|
||||
register_nav_menus( [ 'menu-2' => esc_html__( 'Footer', 'hello-elementor' ) ] );
|
||||
}
|
||||
|
||||
if ( apply_filters( 'hello_elementor_post_type_support', true ) ) {
|
||||
add_post_type_support( 'page', 'excerpt' );
|
||||
}
|
||||
|
||||
if ( apply_filters( 'hello_elementor_add_theme_support', true ) ) {
|
||||
add_theme_support( 'post-thumbnails' );
|
||||
add_theme_support( 'automatic-feed-links' );
|
||||
add_theme_support( 'title-tag' );
|
||||
add_theme_support(
|
||||
'html5',
|
||||
[
|
||||
'search-form',
|
||||
'comment-form',
|
||||
'comment-list',
|
||||
'gallery',
|
||||
'caption',
|
||||
'script',
|
||||
'style',
|
||||
]
|
||||
);
|
||||
add_theme_support(
|
||||
'custom-logo',
|
||||
[
|
||||
'height' => 100,
|
||||
'width' => 350,
|
||||
'flex-height' => true,
|
||||
'flex-width' => true,
|
||||
]
|
||||
);
|
||||
add_theme_support( 'align-wide' );
|
||||
add_theme_support( 'responsive-embeds' );
|
||||
|
||||
/*
|
||||
* Editor Styles
|
||||
*/
|
||||
add_theme_support( 'editor-styles' );
|
||||
add_editor_style( 'editor-styles.css' );
|
||||
|
||||
/*
|
||||
* WooCommerce.
|
||||
*/
|
||||
if ( apply_filters( 'hello_elementor_add_woocommerce_support', true ) ) {
|
||||
// WooCommerce in general.
|
||||
add_theme_support( 'woocommerce' );
|
||||
// Enabling WooCommerce product gallery features (are off by default since WC 3.0.0).
|
||||
// zoom.
|
||||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
// lightbox.
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
// swipe.
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
add_action( 'after_setup_theme', 'hello_elementor_setup' );
|
||||
|
||||
function hello_maybe_update_theme_version_in_db() {
|
||||
$theme_version_option_name = 'hello_theme_version';
|
||||
// The theme version saved in the database.
|
||||
$hello_theme_db_version = get_option( $theme_version_option_name );
|
||||
|
||||
// If the 'hello_theme_version' option does not exist in the DB, or the version needs to be updated, do the update.
|
||||
if ( ! $hello_theme_db_version || version_compare( $hello_theme_db_version, HELLO_ELEMENTOR_VERSION, '<' ) ) {
|
||||
update_option( $theme_version_option_name, HELLO_ELEMENTOR_VERSION );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'hello_elementor_display_header_footer' ) ) {
|
||||
/**
|
||||
* Check whether to display header footer.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function hello_elementor_display_header_footer() {
|
||||
$hello_elementor_header_footer = true;
|
||||
|
||||
return apply_filters( 'hello_elementor_header_footer', $hello_elementor_header_footer );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'hello_elementor_scripts_styles' ) ) {
|
||||
/**
|
||||
* Theme Scripts & Styles.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function hello_elementor_scripts_styles() {
|
||||
$min_suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
|
||||
if ( apply_filters( 'hello_elementor_enqueue_style', true ) ) {
|
||||
wp_enqueue_style(
|
||||
'hello-elementor',
|
||||
get_template_directory_uri() . '/style' . $min_suffix . '.css',
|
||||
[],
|
||||
HELLO_ELEMENTOR_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
if ( apply_filters( 'hello_elementor_enqueue_theme_style', true ) ) {
|
||||
wp_enqueue_style(
|
||||
'hello-elementor-theme-style',
|
||||
get_template_directory_uri() . '/theme' . $min_suffix . '.css',
|
||||
[],
|
||||
HELLO_ELEMENTOR_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
if ( hello_elementor_display_header_footer() ) {
|
||||
wp_enqueue_style(
|
||||
'hello-elementor-header-footer',
|
||||
get_template_directory_uri() . '/header-footer' . $min_suffix . '.css',
|
||||
[],
|
||||
HELLO_ELEMENTOR_VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
add_action( 'wp_enqueue_scripts', 'hello_elementor_scripts_styles' );
|
||||
|
||||
if ( ! function_exists( 'hello_elementor_register_elementor_locations' ) ) {
|
||||
/**
|
||||
* Register Elementor Locations.
|
||||
*
|
||||
* @param ElementorPro\Modules\ThemeBuilder\Classes\Locations_Manager $elementor_theme_manager theme manager.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function hello_elementor_register_elementor_locations( $elementor_theme_manager ) {
|
||||
if ( apply_filters( 'hello_elementor_register_elementor_locations', true ) ) {
|
||||
$elementor_theme_manager->register_all_core_location();
|
||||
}
|
||||
}
|
||||
}
|
||||
add_action( 'elementor/theme/register_locations', 'hello_elementor_register_elementor_locations' );
|
||||
|
||||
if ( ! function_exists( 'hello_elementor_content_width' ) ) {
|
||||
/**
|
||||
* Set default content width.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function hello_elementor_content_width() {
|
||||
$GLOBALS['content_width'] = apply_filters( 'hello_elementor_content_width', 800 );
|
||||
}
|
||||
}
|
||||
add_action( 'after_setup_theme', 'hello_elementor_content_width', 0 );
|
||||
|
||||
if ( ! function_exists( 'hello_elementor_add_description_meta_tag' ) ) {
|
||||
/**
|
||||
* Add description meta tag with excerpt text.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function hello_elementor_add_description_meta_tag() {
|
||||
if ( ! apply_filters( 'hello_elementor_description_meta_tag', true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! is_singular() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post = get_queried_object();
|
||||
if ( empty( $post->post_excerpt ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<meta name="description" content="' . esc_attr( wp_strip_all_tags( $post->post_excerpt ) ) . '">' . "\n";
|
||||
}
|
||||
}
|
||||
add_action( 'wp_head', 'hello_elementor_add_description_meta_tag' );
|
||||
|
||||
// Admin notice
|
||||
if ( is_admin() ) {
|
||||
require get_template_directory() . '/includes/admin-functions.php';
|
||||
}
|
||||
|
||||
// Settings page
|
||||
require get_template_directory() . '/includes/settings-functions.php';
|
||||
|
||||
// Header & footer styling option, inside Elementor
|
||||
require get_template_directory() . '/includes/elementor-functions.php';
|
||||
|
||||
if ( ! function_exists( 'hello_elementor_customizer' ) ) {
|
||||
// Customizer controls
|
||||
function hello_elementor_customizer() {
|
||||
if ( ! is_customize_preview() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! hello_elementor_display_header_footer() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require get_template_directory() . '/includes/customizer-functions.php';
|
||||
}
|
||||
}
|
||||
add_action( 'init', 'hello_elementor_customizer' );
|
||||
|
||||
if ( ! function_exists( 'hello_elementor_check_hide_title' ) ) {
|
||||
/**
|
||||
* Check whether to display the page title.
|
||||
*
|
||||
* @param bool $val default value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function hello_elementor_check_hide_title( $val ) {
|
||||
if ( defined( 'ELEMENTOR_VERSION' ) ) {
|
||||
$current_doc = Elementor\Plugin::instance()->documents->get( get_the_ID() );
|
||||
if ( $current_doc && 'yes' === $current_doc->get_settings( 'hide_title' ) ) {
|
||||
$val = false;
|
||||
}
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
add_filter( 'hello_elementor_page_title', 'hello_elementor_check_hide_title' );
|
||||
|
||||
/**
|
||||
* BC:
|
||||
* In v2.7.0 the theme removed the `hello_elementor_body_open()` from `header.php` replacing it with `wp_body_open()`.
|
||||
* The following code prevents fatal errors in child themes that still use this function.
|
||||
*/
|
||||
if ( ! function_exists( 'hello_elementor_body_open' ) ) {
|
||||
function hello_elementor_body_open() {
|
||||
wp_body_open();
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter les champs ACF à l'API REST pour les pages
|
||||
add_action('rest_api_init', function () {
|
||||
register_rest_field(
|
||||
'page',
|
||||
'acf',
|
||||
[
|
||||
'get_callback' => function ($object) {
|
||||
return get_fields($object['id']);
|
||||
},
|
||||
'schema' => null,
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// API RANK SEO
|
||||
add_filter('rest_prepare_page', function ($response, $post) {
|
||||
$seo_data = [
|
||||
'rank_math_title' => get_post_meta($post->ID, 'rank_math_title', true),
|
||||
'rank_math_description' => get_post_meta($post->ID, 'rank_math_description', true),
|
||||
];
|
||||
|
||||
$response->data = array_merge($response->data, $seo_data);
|
||||
|
||||
return $response;
|
||||
}, 10, 2);
|
||||
|
||||
add_action('rest_api_init', function () {
|
||||
register_rest_route('custom/v1', '/contact', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => 'handle_contact_form',
|
||||
'permission_callback' => '__return_true', // Permet l'accès public
|
||||
));
|
||||
});
|
||||
|
||||
// Formulaire
|
||||
function register_contact_endpoint() {
|
||||
register_rest_route('custom/v1', '/contact', [
|
||||
'methods' => 'POST',
|
||||
'callback' => 'handle_contact_form',
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
}
|
||||
add_action('rest_api_init', 'register_contact_endpoint');
|
||||
|
||||
function handle_contact_form($request) {
|
||||
$params = $request->get_json_params();
|
||||
|
||||
$name = sanitize_text_field($params['name'] ?? '');
|
||||
$email = sanitize_email($params['email'] ?? '');
|
||||
$subject = sanitize_text_field($params['subject'] ?? '');
|
||||
$message = sanitize_textarea_field($params['message'] ?? '');
|
||||
|
||||
if (empty($name) || empty($email) || empty($subject) || empty($message)) {
|
||||
return new WP_Error(
|
||||
'incomplete_fields',
|
||||
'Tous les champs doivent être remplis.',
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
// Logique pour envoyer l'e-mail ou enregistrer les données
|
||||
$to = get_option('admin_email'); // Adresse e-mail de l'administrateur WordPress
|
||||
$headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $name . ' <' . $email . '>'];
|
||||
|
||||
$mail_sent = wp_mail($to, $subject, nl2br($message), $headers);
|
||||
|
||||
if (!$mail_sent) {
|
||||
return new WP_Error('email_not_sent', 'Le message n’a pas pu être envoyé.', ['status' => 500]);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Message envoyé avec succès.',
|
||||
];
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user