Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf77437c89 | |||
| 841161cb9b | |||
| 81ac3d831e | |||
| c3ab2b54ca | |||
| 1e4dd3b475 | |||
| cc641c5f5d | |||
| e605d3915d | |||
| ba6603fc6f | |||
| 9eca9041fb | |||
| dc3c6a566b |
@@ -22,5 +22,3 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
.env
|
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
<IfModule mod_headers.c>
|
||||||
|
Header set Access-Control-Allow-Origin "*"
|
||||||
|
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE"
|
||||||
|
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
<IfModule mod_rewrite.c>
|
<IfModule mod_rewrite.c>
|
||||||
RewriteEngine On
|
RewriteEngine On
|
||||||
RewriteBase /
|
RewriteBase /
|
||||||
|
|||||||
Generated
+469
-355
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,6 @@
|
|||||||
"@mui/icons-material": "^6.3.0",
|
"@mui/icons-material": "^6.3.0",
|
||||||
"@mui/material": "^6.3.0",
|
"@mui/material": "^6.3.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"dotenv": "^16.4.7",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Box, Typography, Button, CircularProgress } from "@mui/material";
|
||||||
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
|
import api from "../api";
|
||||||
|
|
||||||
|
const PostDetails = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const [post, setPost] = useState(null);
|
||||||
|
const [image, setImage] = useState(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPost = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`wp/v2/posts/${id}?_fields=title,content,featured_media,date`);
|
||||||
|
setPost(response.data);
|
||||||
|
|
||||||
|
// Récupération de l'image
|
||||||
|
if (response.data.featured_media) {
|
||||||
|
const mediaResponse = await api.get(`wp/v2/media/${response.data.featured_media}`);
|
||||||
|
setImage(mediaResponse.data.source_url);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération de l'article :", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchPost();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return <CircularProgress sx={{ display: "block", margin: "auto", mt: 5 }} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ padding: "40px 20px",display:"flex", flexDirection:"column" }}>
|
||||||
|
<Button onClick={() => navigate(-1)} variant="outlined" sx={{ mt:5, mb: 3, maxWidth:"200px" }}>
|
||||||
|
⬅ Retour
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Typography variant="h1" sx={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
mb: 4,
|
||||||
|
fontSize: { xs: '3rem', md: '3rem', lg: '3rem' }, // Responsive
|
||||||
|
}}>
|
||||||
|
{post.title.rendered}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ display:"flex", alignItems:"center", justifyContent:"center", padding: "40px 20px",margin:"auto" }}>
|
||||||
|
{image && (
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src={image}
|
||||||
|
alt={post.title.rendered}
|
||||||
|
sx={{ width: "33%", maxHeight: "400px", objectFit: "cover", borderRadius: "8px", mb: 3 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
<Typography variant="body1" sx={{ maxWidth: "800px", mx: "auto" }} dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PostDetails;
|
||||||
+13
-6
@@ -1,12 +1,19 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
|
|
||||||
// Création de l'instance Axios
|
const API_URL = "https://preprod.octopusdesign.fr/api-octopus/server/wp-json";
|
||||||
|
|
||||||
|
// 🔹 Instance Axios pour éviter de répéter l'URL
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: 'https://preprod.octopusdesign.fr/api-octopus/server/wp-json',
|
baseURL: API_URL,
|
||||||
withCredentials: true,
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🔹 Fonction générique pour récupérer le token
|
||||||
|
export const getToken = () => {
|
||||||
|
return sessionStorage.getItem("custom_token");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 🔹 Exporter l'instance Axios
|
||||||
// Exportation de l'instance Axios par défaut
|
|
||||||
export default api;
|
export default api;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const API_URL = "https://preprod.octopusdesign.fr/api-octopus/server/wp-json";
|
||||||
|
|
||||||
|
// ✅ Connexion avec le mot de passe d’application
|
||||||
|
export const loginWithAppPassword = async (username, appPassword) => {
|
||||||
|
try {
|
||||||
|
const credentials = `${username}:${appPassword}`;
|
||||||
|
const encodedCredentials = btoa(credentials); // Encodage en Base64
|
||||||
|
|
||||||
|
const response = await axios.get(`${API_URL}/wp/v2/users/me`, {
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Basic ${encodedCredentials}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("✅ Connexion réussie :", response.data);
|
||||||
|
sessionStorage.setItem("wp_app_token", encodedCredentials);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Erreur lors de la connexion :", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ✅ Récupération du token stocké
|
||||||
|
export const getToken = () => {
|
||||||
|
return sessionStorage.getItem("wp_app_token") || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ✅ Déconnexion
|
||||||
|
export const logout = () => {
|
||||||
|
console.log("🚪 Déconnexion...");
|
||||||
|
sessionStorage.removeItem("wp_app_token");
|
||||||
|
};
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
.critical-alert-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.critical-alert-box {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 0 10px rgba(255, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import "./CriticalAlert.css";
|
|
||||||
|
|
||||||
const CriticalAlert = () => {
|
|
||||||
const location = useLocation();
|
|
||||||
const [isActive, setIsActive] = useState(false);
|
|
||||||
const [opacity, setOpacity] = useState(1);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const updateOpacity = () => {
|
|
||||||
const alertStatus = localStorage.getItem("criticalAlertActive");
|
|
||||||
const startTime = localStorage.getItem("criticalAlertStartTime");
|
|
||||||
const totalDuration = parseInt(localStorage.getItem("totalDuration") || "15", 10);
|
|
||||||
|
|
||||||
if (location.pathname === "/backtime") {
|
|
||||||
setOpacity(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alertStatus === "yes" && startTime) {
|
|
||||||
setIsActive(true);
|
|
||||||
|
|
||||||
const startTimestamp = parseInt(startTime, 10);
|
|
||||||
const now = Date.now();
|
|
||||||
const elapsedTime = now - startTimestamp;
|
|
||||||
const totalTime = totalDuration * 24 * 60 * 60 * 1000;
|
|
||||||
const remainingTime = totalTime - elapsedTime;
|
|
||||||
|
|
||||||
if (remainingTime <= 0) {
|
|
||||||
setOpacity(0);
|
|
||||||
} else {
|
|
||||||
const newOpacity = 1 - elapsedTime / totalTime;
|
|
||||||
setOpacity(newOpacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
updateOpacity();
|
|
||||||
const interval = setInterval(updateOpacity, 10000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [location]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
isActive && location.pathname !== "/backtime" && (
|
|
||||||
<style>
|
|
||||||
{`body { opacity: ${opacity}; transition: opacity 10s linear; }`}
|
|
||||||
</style>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CriticalAlert;
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { deletePost } from "../wordpress";
|
||||||
|
|
||||||
|
function DeletePost({ postId, onDelete }) {
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (window.confirm("Es-tu sûr de vouloir supprimer cet article ?")) {
|
||||||
|
const success = await deletePost(postId);
|
||||||
|
if (success) {
|
||||||
|
alert("Article supprimé avec succès !");
|
||||||
|
onDelete(); // Rafraîchir la liste
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <button onClick={handleDelete}>🗑️ Supprimer</button>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeletePost;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { updatePost } from "../wordpress";
|
||||||
|
|
||||||
|
function EditPost({ post }) {
|
||||||
|
const [title, setTitle] = useState(post.title.rendered);
|
||||||
|
const [content, setContent] = useState(post.content.rendered);
|
||||||
|
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
const success = await updatePost(post.id, title, content);
|
||||||
|
if (success) {
|
||||||
|
alert("Article mis à jour !");
|
||||||
|
} else {
|
||||||
|
alert("Échec de la mise à jour.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Modifier l’article</h1>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
value={content}
|
||||||
|
onChange={(e) => setContent(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button onClick={handleUpdate}>Mettre à jour</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPost;
|
||||||
@@ -84,13 +84,14 @@ const Header = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar
|
<AppBar
|
||||||
position="static"
|
position="fixed"
|
||||||
color="transparent"
|
color="transparent"
|
||||||
elevation={0}
|
elevation={0}
|
||||||
sx={{
|
sx={{
|
||||||
background: "linear-gradient(to right, #294A9A, #3158b3)",
|
background: "linear-gradient(to right, #294A9A, #3158b3)",
|
||||||
color: "white",
|
color: "white",
|
||||||
transition: "all 0.3s ease",
|
transition: "all 0.3s ease",
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
@@ -170,7 +171,7 @@ const Header = () => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/services/formation-ia"
|
to="/services/structure-beton-charpente-metallique-bois"
|
||||||
onClick={handleMenuClose}
|
onClick={handleMenuClose}
|
||||||
>
|
>
|
||||||
<WorkIcon sx={{ marginRight: 1 }} />
|
<WorkIcon sx={{ marginRight: 1 }} />
|
||||||
@@ -212,6 +213,19 @@ const Header = () => {
|
|||||||
>
|
>
|
||||||
À Propos
|
À Propos
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to="/bureauEtude"
|
||||||
|
color="inherit"
|
||||||
|
sx={{
|
||||||
|
fontWeight: location.pathname === "/bureauEtude" ? "bold" : "normal",
|
||||||
|
textDecoration:
|
||||||
|
location.pathname === "/bureauEtude" ? "underline" : "none",
|
||||||
|
...commonButtonStyles,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Bureau d'étude
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/contact"
|
to="/contact"
|
||||||
|
|||||||
@@ -1,11 +1,345 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Avatar,
|
||||||
|
} from "@mui/material";
|
||||||
|
import BusinessIcon from "@mui/icons-material/Business";
|
||||||
|
import BuildIcon from "@mui/icons-material/Build";
|
||||||
|
import FlashOnIcon from "@mui/icons-material/FlashOn";
|
||||||
|
import SecurityIcon from "@mui/icons-material/Security";
|
||||||
|
import EngineeringIcon from "@mui/icons-material/Engineering";
|
||||||
|
import AssessmentIcon from "@mui/icons-material/Assessment";
|
||||||
|
import ComputerIcon from "@mui/icons-material/Computer";
|
||||||
|
import LibraryBooksIcon from "@mui/icons-material/LibraryBooks";
|
||||||
|
import MoneyIcon from "@mui/icons-material/Money";
|
||||||
|
import PrintIcon from "@mui/icons-material/Print";
|
||||||
|
import StraightenIcon from "@mui/icons-material/Straighten";
|
||||||
|
import SettingsInputAntenna from "@mui/icons-material/SettingsInputAntenna";
|
||||||
|
import BookIcon from "@mui/icons-material/Book";
|
||||||
|
|
||||||
const About = () => {
|
const About = () => {
|
||||||
|
const competences = [
|
||||||
|
{
|
||||||
|
icon: <BusinessIcon />,
|
||||||
|
title: "Bureau d’études et Maitrise d’œuvre",
|
||||||
|
link: "/services/maitrise-oeuvre",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <BuildIcon />,
|
||||||
|
title: "La structure béton / métallique / bois",
|
||||||
|
link: "/services/structure-beton-charpente-metallique-bois",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <FlashOnIcon />,
|
||||||
|
title: "L’électricité",
|
||||||
|
link: "/services/electricite",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <SecurityIcon />,
|
||||||
|
title: "Sécurité incendie (SSI)",
|
||||||
|
link: "/services/securite-incendie",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <EngineeringIcon />,
|
||||||
|
title: "Expertises TCE",
|
||||||
|
link: "/services/expertises-tce",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <AssessmentIcon />,
|
||||||
|
title: "Diagnostics technique",
|
||||||
|
link: "/services/diagnostics-technique",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const team = [
|
||||||
|
{
|
||||||
|
name: "Ingénieur consultant Génie Civil",
|
||||||
|
role: "Spécialiste Structures en béton armé, charpentes bois et métalliques",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ingénieur Électricité",
|
||||||
|
role: "courant Forts et Faibles, expert bâtiment TCE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Technicienne spécialiste Structures",
|
||||||
|
role: "Béton armé, TCE, DAO-CAO, dessinatrice TCE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Assistante de gestion",
|
||||||
|
role: "Comptable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "consultants",
|
||||||
|
role: "spécialités tel que thermique, SSI",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const infrastructure = [
|
||||||
|
{
|
||||||
|
icon: <ComputerIcon />,
|
||||||
|
title: "CAO-DAO",
|
||||||
|
description: "Autocad & ZWCAD Pro",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <ComputerIcon />,
|
||||||
|
title: "Modélisation",
|
||||||
|
description: "Sketchup 3D",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <LibraryBooksIcon />,
|
||||||
|
title: "Calculs techniques",
|
||||||
|
description: "Progiciel interne ou programme du CACT (tango, spot)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <MoneyIcon />,
|
||||||
|
title: "Logiciel de comptabilité",
|
||||||
|
description: "EBP",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const materiel = [
|
||||||
|
{
|
||||||
|
icon: <ComputerIcon />,
|
||||||
|
title: "Parc informatique",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <PrintIcon />,
|
||||||
|
title: "Traceur plans",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <StraightenIcon />,
|
||||||
|
title: "Pied à coulisse",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <SettingsInputAntenna />,
|
||||||
|
title: "détecteurs de métaux",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <BookIcon />,
|
||||||
|
title: "Bibliothèques techniques (DTU…)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <BookIcon />,
|
||||||
|
title: "Bibliothèques commerciales",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box sx={{ padding: "40px 20px" }}>
|
||||||
<h1>À propos</h1>
|
{/* En-tête */}
|
||||||
<p>Bienvenue sur la page À propos.</p>
|
<Typography
|
||||||
</div>
|
variant="h1"
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: "3rem", md: "3rem", lg: "3rem" },
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
mb: 4, mt:5
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
La société IN3
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
textAlign: "center",
|
||||||
|
mb: 6,
|
||||||
|
maxWidth: "800px",
|
||||||
|
mx: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Depuis 2001, Monsieur Stéphane Seillé gère la société qu’il a créée au
|
||||||
|
Mans (Sarthe). L’entreprise a su développer son expertise autour des
|
||||||
|
activités du bureau d’études et de la maîtrise d’œuvre.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Compétences */}
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: "2rem", md: "2rem", lg: "2rem" },
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
mb: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Nos Compétences
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={4} justifyContent="center">
|
||||||
|
{competences.map((competence, index) => (
|
||||||
|
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||||
|
<a href={competence.link} style={{ textDecoration: "none" }}>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
textAlign: "center",
|
||||||
|
padding: 2,
|
||||||
|
transition: "all 0.3s ease-in-out",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#0e467f",
|
||||||
|
color: "#ffffff",
|
||||||
|
transform: "scale(1.05)",
|
||||||
|
boxShadow: 6,
|
||||||
|
},
|
||||||
|
"&:hover svg": { fill: "#ffffff" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="span" sx={{ mb: 1 }}>
|
||||||
|
{competence.icon}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: "1.1rem", md: "1.1rem", lg: "1.1rem" },
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{competence.title}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</a>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Équipe - Maintenant sur 2 colonnes */}
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
background: "linear-gradient(to bottom, #0e467f, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
mt: 6,
|
||||||
|
mb: 3,
|
||||||
|
color: "#ffffff",
|
||||||
|
pt: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Notre Équipe
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ textAlign: "center", mt: 1, mb: 3, color: "#ffffff", pt: 2 }}
|
||||||
|
>
|
||||||
|
L’équipe d’IN3 est composée de collaborateurs experts dans leurs
|
||||||
|
domaines :
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={4} justifyContent="center">
|
||||||
|
{team.map((member, index) => (
|
||||||
|
<Grid item xs={12} sm={6} key={index}>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
textAlign: "center",
|
||||||
|
p: 2,
|
||||||
|
margin: 2,
|
||||||
|
height: "100",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<Avatar sx={{ width: 56, height: 56, margin: "auto" }} />
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: "1.1rem", md: "1.1rem", lg: "1.1rem" },
|
||||||
|
fontWeight: "bold",
|
||||||
|
mt: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{member.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">{member.role}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Infrastructure */}
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={{ fontWeight: "bold", textAlign: "center", mt: 6, mb: 3 }}
|
||||||
|
>
|
||||||
|
Notre Infrastructure
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={4} justifyContent="center">
|
||||||
|
{infrastructure.map((infra, index) => (
|
||||||
|
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||||
|
<Card sx={{ textAlign: "center", padding: 2 }}>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="span" sx={{ mb: 1 }}>
|
||||||
|
{infra.icon}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: "1.1rem", md: "1.1rem", lg: "1.1rem" },
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{infra.title}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">{infra.description}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
background: "linear-gradient(to top, #6d6e6f, transparent)",
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: "2rem", md: "2rem", lg: "2rem" },
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
mt: 6,
|
||||||
|
mb: 3,
|
||||||
|
p: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Nous sommes équipés de matériel de dernière génération :
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={4} justifyContent="center">
|
||||||
|
{materiel.map((infra, index) => (
|
||||||
|
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||||
|
<Card sx={{ textAlign: "center", p: 2, margin: 2 }}>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="span" sx={{ mb: 1 }}>
|
||||||
|
{infra.icon}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: "1.1rem", md: "1.1rem", lg: "1.1rem" },
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{infra.title}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">{infra.description}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
|
|
||||||
const AdminDashboard = () => {
|
|
||||||
const [isActive, setIsActive] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const alertStatus = localStorage.getItem("criticalAlertActive");
|
|
||||||
setIsActive(alertStatus === "yes");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const activateAlert = () => {
|
|
||||||
localStorage.setItem("criticalAlertActive", "yes");
|
|
||||||
localStorage.setItem("criticalAlertStartTime", Date.now().toString());
|
|
||||||
setIsActive(true);
|
|
||||||
window.location.reload(); // Recharge la page pour appliquer les changements
|
|
||||||
};
|
|
||||||
|
|
||||||
const deactivateAlert = () => {
|
|
||||||
localStorage.removeItem("criticalAlertActive");
|
|
||||||
localStorage.removeItem("criticalAlertStartTime");
|
|
||||||
setIsActive(false);
|
|
||||||
window.location.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Panneau Administrateur</h2>
|
|
||||||
<button
|
|
||||||
onClick={activateAlert}
|
|
||||||
style={{ backgroundColor: "red", color: "white", padding: "10px", marginRight: "10px" }}
|
|
||||||
disabled={isActive}
|
|
||||||
>
|
|
||||||
Activer l'Alerte Critique
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={deactivateAlert}
|
|
||||||
style={{ backgroundColor: "green", color: "white", padding: "10px" }}
|
|
||||||
disabled={!isActive}
|
|
||||||
>
|
|
||||||
Désactiver l'Alerte Critique
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdminDashboard;
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
/* 🔥 Design moderne pour la page Backtime */
|
|
||||||
.backtime-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100vh;
|
|
||||||
background: linear-gradient(135deg, #1e1e2f, #3b3b58);
|
|
||||||
color: white;
|
|
||||||
font-family: "Poppins", sans-serif;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-button {
|
|
||||||
background: #e74c3c;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-button:hover {
|
|
||||||
background: #c0392b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.countdown {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2);
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.countdown h3 {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #f1c40f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-setting {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-setting label {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-setting input {
|
|
||||||
width: 60px;
|
|
||||||
padding: 5px;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
text-align: center;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-container {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-container span {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🎚️ Switch */
|
|
||||||
.switch {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
width: 60px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input {
|
|
||||||
opacity: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #ccc;
|
|
||||||
transition: 0.4s;
|
|
||||||
border-radius: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider:before {
|
|
||||||
position: absolute;
|
|
||||||
content: "";
|
|
||||||
height: 22px;
|
|
||||||
width: 22px;
|
|
||||||
left: 4px;
|
|
||||||
bottom: 4px;
|
|
||||||
background-color: white;
|
|
||||||
transition: 0.4s;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider {
|
|
||||||
background-color: #2ecc71;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider:before {
|
|
||||||
transform: translateX(26px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🔐 Modernisation du formulaire de connexion */
|
|
||||||
.login-container {
|
|
||||||
background: rgba(0, 0, 0, 0.8); /* Fond semi-transparent pour un effet élégant */
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 12px;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0px 10px 25px rgba(0, 0, 0, 0.3); /* Ombre plus profonde pour effet moderne */
|
|
||||||
backdrop-filter: blur(10px); /* Effet flou pour un look premium */
|
|
||||||
width: 320px;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-container:hover {
|
|
||||||
transform: scale(1.02); /* Effet léger d’agrandissement au survol */
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-container h2 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: #f1c40f; /* Couleur dorée pour mettre en avant le titre */
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-container input,
|
|
||||||
.login-button {
|
|
||||||
width: 100%; /* Assure que les deux éléments prennent la même largeur */
|
|
||||||
max-width: 280px; /* Définit une largeur max pour éviter qu'ils deviennent trop grands */
|
|
||||||
display: block; /* Évite les décalages */
|
|
||||||
margin: 0 auto; /* Centre les éléments */
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-container input {
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 1rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-container input::placeholder {
|
|
||||||
color: #ddd;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button {
|
|
||||||
background: linear-gradient(135deg, #3498db, #2980b9);
|
|
||||||
color: white;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-top: 10px; /* Ajoute un petit espace entre le champ et le bouton */
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button:hover {
|
|
||||||
background: linear-gradient(135deg, #2980b9, #1c6ea4);
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🎨 Design amélioré du menu déroulant */
|
|
||||||
.duration-selector {
|
|
||||||
margin-top: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.duration-selector label {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #f0c040;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🌟 Style du select */
|
|
||||||
.duration-selector select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: white;
|
|
||||||
background: linear-gradient(135deg, #222831, #393e46);
|
|
||||||
border: 2px solid #f0c040;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🎨 Effet au survol */
|
|
||||||
.duration-selector select:hover {
|
|
||||||
background: linear-gradient(135deg, #393e46, #222831);
|
|
||||||
border-color: #ffcc00;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🔽 Apparence des options */
|
|
||||||
.duration-selector select option {
|
|
||||||
background: #222831;
|
|
||||||
color: white;
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import { Helmet } from "react-helmet";
|
|
||||||
import "./Backtime.css";
|
|
||||||
|
|
||||||
const PASSWORD_HASH = import.meta.env.VITE_PASSWORD_HASH;
|
|
||||||
|
|
||||||
const Backtime = () => {
|
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
||||||
const [inputPassword, setInputPassword] = useState("");
|
|
||||||
const [timeRemaining, setTimeRemaining] = useState(null);
|
|
||||||
const [selectedDuration, setSelectedDuration] = useState(() => {
|
|
||||||
return parseInt(localStorage.getItem("criticalAlertDuration")) || 15;
|
|
||||||
});
|
|
||||||
const [isActive, setIsActive] = useState(() => {
|
|
||||||
return localStorage.getItem("criticalAlertActive") === "yes";
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const storedAuth = localStorage.getItem("backtimeAuth");
|
|
||||||
if (storedAuth === "true") {
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTimeRemaining();
|
|
||||||
const interval = setInterval(updateTimeRemaining, 1000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isActive) {
|
|
||||||
localStorage.setItem("criticalAlertDuration", selectedDuration);
|
|
||||||
if (!localStorage.getItem("criticalAlertStartTime")) {
|
|
||||||
localStorage.setItem("criticalAlertStartTime", Date.now().toString());
|
|
||||||
}
|
|
||||||
updateTimeRemaining();
|
|
||||||
}
|
|
||||||
}, [selectedDuration]);
|
|
||||||
|
|
||||||
const hashPassword = async (password) => {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const data = encoder.encode(password);
|
|
||||||
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
||||||
return Array.from(new Uint8Array(hashBuffer))
|
|
||||||
.map((b) => b.toString(16).padStart(2, "0"))
|
|
||||||
.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogin = async () => {
|
|
||||||
const hashedInput = await hashPassword(inputPassword);
|
|
||||||
if (hashedInput === PASSWORD_HASH) {
|
|
||||||
localStorage.setItem("backtimeAuth", "true");
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
} else {
|
|
||||||
alert("Mot de passe incorrect !");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
localStorage.removeItem("backtimeAuth");
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateTimeRemaining = () => {
|
|
||||||
const startTime = localStorage.getItem("criticalAlertStartTime");
|
|
||||||
|
|
||||||
if (startTime) {
|
|
||||||
const startTimestamp = parseInt(startTime, 10);
|
|
||||||
const now = Date.now();
|
|
||||||
const elapsedTime = now - startTimestamp;
|
|
||||||
const totalTime = selectedDuration * 24 * 60 * 60 * 1000;
|
|
||||||
const remainingTime = totalTime - elapsedTime;
|
|
||||||
|
|
||||||
if (remainingTime <= 0) {
|
|
||||||
setTimeRemaining("Le site est complètement transparent !");
|
|
||||||
localStorage.setItem("criticalAlertActive", "no"); // Désactive le module si le temps est écoulé
|
|
||||||
} else {
|
|
||||||
const days = Math.floor(remainingTime / (1000 * 60 * 60 * 24));
|
|
||||||
const hours = Math.floor((remainingTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
||||||
const minutes = Math.floor((remainingTime % (1000 * 60 * 60)) / (1000 * 60));
|
|
||||||
const seconds = Math.floor((remainingTime % (1000 * 60)) / 1000);
|
|
||||||
setTimeRemaining(`${days}j ${hours}h ${minutes}m ${seconds}s`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setTimeRemaining("Aucune alerte activée");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDurationChange = (event) => {
|
|
||||||
const newDuration = parseInt(event.target.value, 10);
|
|
||||||
setSelectedDuration(newDuration);
|
|
||||||
if (isActive) {
|
|
||||||
localStorage.setItem("criticalAlertDuration", newDuration);
|
|
||||||
if (!localStorage.getItem("criticalAlertStartTime")) {
|
|
||||||
localStorage.setItem("criticalAlertStartTime", Date.now().toString());
|
|
||||||
}
|
|
||||||
updateTimeRemaining();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="backtime-container">
|
|
||||||
<Helmet>
|
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
|
||||||
<title>Page introuvable</title>
|
|
||||||
<style>{`body { opacity: 1 !important; }`}</style>
|
|
||||||
</Helmet>
|
|
||||||
|
|
||||||
{isAuthenticated ? (
|
|
||||||
<>
|
|
||||||
<h2>Gestion du module d'alerte critique</h2>
|
|
||||||
<button onClick={handleLogout} className="logout-button">Déconnexion</button>
|
|
||||||
|
|
||||||
<div className="switch-container">
|
|
||||||
<SwitchControl isActive={isActive} setIsActive={setIsActive} selectedDuration={selectedDuration} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="duration-selector">
|
|
||||||
<label>Durée du processus :</label>
|
|
||||||
<select value={selectedDuration} onChange={handleDurationChange}>
|
|
||||||
{Array.from({ length: 15 }, (_, i) => i + 1).map((day) => (
|
|
||||||
<option key={day} value={day}>{day} jours</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="countdown">
|
|
||||||
<h3>Temps restant avant transparence totale :</h3>
|
|
||||||
<p>{timeRemaining}</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="login-container">
|
|
||||||
<h2>Accès Restreint</h2>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder="Entrez le mot de passe"
|
|
||||||
value={inputPassword}
|
|
||||||
onChange={(e) => setInputPassword(e.target.value)}
|
|
||||||
/>
|
|
||||||
<button onClick={handleLogin} className="login-button">Valider</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SwitchControl = ({ isActive, setIsActive, selectedDuration }) => {
|
|
||||||
useEffect(() => {
|
|
||||||
const alertStatus = localStorage.getItem("criticalAlertActive");
|
|
||||||
setIsActive(alertStatus === "yes");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const toggleAlert = () => {
|
|
||||||
if (!isActive) {
|
|
||||||
localStorage.setItem("criticalAlertActive", "yes");
|
|
||||||
if (!localStorage.getItem("criticalAlertStartTime")) {
|
|
||||||
localStorage.setItem("criticalAlertStartTime", Date.now().toString());
|
|
||||||
}
|
|
||||||
localStorage.setItem("criticalAlertDuration", selectedDuration);
|
|
||||||
} else {
|
|
||||||
localStorage.setItem("criticalAlertActive", "no");
|
|
||||||
localStorage.removeItem("criticalAlertStartTime");
|
|
||||||
}
|
|
||||||
setIsActive(!isActive);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="switch-container">
|
|
||||||
<label className="switch">
|
|
||||||
<input type="checkbox" checked={isActive} onChange={toggleAlert} />
|
|
||||||
<span className="slider"></span>
|
|
||||||
</label>
|
|
||||||
<span className={`status-indicator ${isActive ? "active" : "inactive"}`}>
|
|
||||||
{isActive ? "Module Actif" : "Module Inactif"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Backtime;
|
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Button,
|
||||||
|
} from "@mui/material";
|
||||||
|
import api from "../../api";
|
||||||
|
|
||||||
|
const BureauEtudes = () => {
|
||||||
|
const [pageData, setPageData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPageData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get("wp/v2/pages/272?_fields=acf"); // Remplace XXX par l'ID WordPress de la page
|
||||||
|
setPageData(response.data.acf);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"Erreur lors de la récupération des données ACF :",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchPageData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!pageData) {
|
||||||
|
return <Typography>Chargement...</Typography>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{}}>
|
||||||
|
{/* Section Héros avec Image de Fond */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundImage: "url('https://picsum.photos/1920/600.webp')",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
color: "white",
|
||||||
|
textAlign: "center",
|
||||||
|
px: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* Présentation Générale */}
|
||||||
|
<Box sx={{ mt: 6, textAlign: "center", px: 4 }}>
|
||||||
|
<Typography variant="h1" sx={{
|
||||||
|
fontSize: { xs: "3rem", md: "3rem", lg: "3.5rem" }, // Responsive
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: 2,
|
||||||
|
}}>
|
||||||
|
Notre Bureau d'Études
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ maxWidth: "900px", mx: "auto" }}>
|
||||||
|
{pageData.introduction}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{/* Domaines d'Intervention */}
|
||||||
|
<Box sx={{ mt: 6, px: 4 }}>
|
||||||
|
<Typography variant="h2" sx={{
|
||||||
|
fontSize: { xs: "2.5rem", md: "2.5rem", lg: "2.5rem" }, // Responsive
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
mb: 3 }}>
|
||||||
|
Nos Domaines d'Intervention
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={4} justifyContent="center">
|
||||||
|
{pageData.liste_des_competences?.map((competence, index) => (
|
||||||
|
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
textAlign: "center",
|
||||||
|
padding: 3,
|
||||||
|
transition: "transform 0.3s, box-shadow 0.3s",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "scale(1.05)",
|
||||||
|
boxShadow: "0px 10px 20px rgba(0, 0, 0, 0.2)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h3" sx={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: 1,
|
||||||
|
fontSize: { xs: "1.8rem", md: "1.8rem", lg: "1.8rem" }, // Responsive
|
||||||
|
}}>
|
||||||
|
{competence.comp_titre}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{
|
||||||
|
textAlign:"center"
|
||||||
|
}}>{competence.comp_description}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Compétences Spécifiques */}
|
||||||
|
<Box sx={{ mt: 6, textAlign: "center", px: 4 }}>
|
||||||
|
<Typography variant="h2" sx={{
|
||||||
|
fontSize: { xs: "2.5rem", md: "2.5rem", lg: "2.5rem" }, // Responsive
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: 3
|
||||||
|
}}>
|
||||||
|
Nos Compétences
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" sx={{ maxWidth: "900px", mx: "auto" }}>
|
||||||
|
{pageData.methodologie}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Engagement et Accompagnement */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mt: 6,
|
||||||
|
py: 6,
|
||||||
|
px: 4,
|
||||||
|
textAlign: "center",
|
||||||
|
background: "linear-gradient(to bottom, #0e467f, #ffffff)",
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h2" sx={{
|
||||||
|
fontSize: { xs: "2.5rem", md: "2.5rem", lg: "2.5rem" }, // Responsive
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: 3
|
||||||
|
}}>
|
||||||
|
Engagement et Accompagnement
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={4} justifyContent="center">
|
||||||
|
{pageData.expertises_specifiques?.map((expertise, index) => (
|
||||||
|
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
textAlign: "center",
|
||||||
|
padding: 3,
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
backdropFilter: "blur(10px)",
|
||||||
|
transition: "transform 0.3s, box-shadow 0.3s",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "scale(1.05)",
|
||||||
|
boxShadow: "0px 10px 20px rgba(255, 255, 255, 0.2)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h3" sx={{
|
||||||
|
fontSize: { xs: "1.8rem", md: "1.8rem", lg: "1.8rem" }, // Responsive
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: 1 }}>
|
||||||
|
{expertise.expertise_nom}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">{expertise.expertise_details}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Call-To-Action */}
|
||||||
|
<Box sx={{ mt: 6, textAlign: "center", mb: 6 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
href="/contact"
|
||||||
|
sx={{
|
||||||
|
padding: "12px 24px",
|
||||||
|
fontSize: "1.2rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
borderRadius: "8px",
|
||||||
|
transition: "background-color 0.3s, transform 0.3s",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#0a3b6b",
|
||||||
|
transform: "scale(1.05)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Nous Contacter
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BureauEtudes;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { uploadImage, createPost } from "../../wordpress";
|
||||||
|
|
||||||
|
function CreatePost() {
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [content, setContent] = useState("");
|
||||||
|
const [file, setFile] = useState(null);
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("📢 Tentative d'upload de l'image...");
|
||||||
|
const imageId = await uploadImage(file);
|
||||||
|
|
||||||
|
console.log("📢 Création du post avec l'image mise en avant...");
|
||||||
|
await createPost(title, content, imageId);
|
||||||
|
|
||||||
|
alert("✅ Post créé avec succès !");
|
||||||
|
} catch (error) {
|
||||||
|
alert("❌ Erreur lors de la création du post.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Créer un post</h2>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Titre du post"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
placeholder="Contenu du post"
|
||||||
|
value={content}
|
||||||
|
onChange={(e) => setContent(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(e) => setFile(e.target.files[0])}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button type="submit">Publier</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreatePost;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
IconButton,
|
||||||
|
} from "@mui/material";
|
||||||
|
import ArticleIcon from "@mui/icons-material/Article";
|
||||||
|
import DashboardIcon from "@mui/icons-material/Dashboard";
|
||||||
|
|
||||||
|
function Dashboard() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
minHeight: "100vh",
|
||||||
|
backgroundImage: "url('https://source.unsplash.com/1600x900/?technology,office')",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "90%",
|
||||||
|
maxWidth: "900px",
|
||||||
|
padding: 4,
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
||||||
|
borderRadius: 3,
|
||||||
|
boxShadow: 6,
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* En-tête */}
|
||||||
|
<Typography variant="h4" sx={{ fontWeight: "bold", mb: 4 }}>
|
||||||
|
<DashboardIcon sx={{ fontSize: 40, color: "#0e467f", mr: 1 }} />
|
||||||
|
Tableau de Bord
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Cartes du Dashboard */}
|
||||||
|
<Grid container spacing={3} justifyContent="center">
|
||||||
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
|
<Card
|
||||||
|
onClick={() => navigate("/posts")}
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
textAlign: "center",
|
||||||
|
padding: 2,
|
||||||
|
transition: "0.3s",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#0e467f",
|
||||||
|
color: "#ffffff",
|
||||||
|
transform: "scale(1.05)",
|
||||||
|
boxShadow: 8,
|
||||||
|
},
|
||||||
|
"&:hover svg": {
|
||||||
|
fill: "#ffffff",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<IconButton sx={{ fontSize: 40, color: "#0e467f" }}>
|
||||||
|
<ArticleIcon fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="h6" sx={{ fontWeight: "bold" }}>
|
||||||
|
Gérer les Articles
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Hero from "../Hero";
|
import Hero from "../Hero";
|
||||||
import SEO from "../SEO";
|
import SEO from "../SEO";
|
||||||
import api from "../../api";
|
import api from "../../api";
|
||||||
@@ -7,6 +7,7 @@ import Expertises from "../Expertises";
|
|||||||
import ConstructSection from "../ConstructSection";
|
import ConstructSection from "../ConstructSection";
|
||||||
import { Box, Grid, Typography, Button, Card, CardContent } from "@mui/material";
|
import { Box, Grid, Typography, Button, Card, CardContent } from "@mui/material";
|
||||||
import Logo from "../../assets/logo-in3-mobil.svg";
|
import Logo from "../../assets/logo-in3-mobil.svg";
|
||||||
|
import Testi from "../Testi";
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const [pageData, setPageData] = useState(null);
|
const [pageData, setPageData] = useState(null);
|
||||||
@@ -61,6 +62,8 @@ const Home = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const { acf, rank_math_title, rank_math_description } = pageData || {};
|
const { acf, rank_math_title, rank_math_description } = pageData || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -106,39 +109,13 @@ const Home = () => {
|
|||||||
expertise3Text={acf?.gdc_expert?.text_expertise_3 || "Text Expertise 3"}
|
expertise3Text={acf?.gdc_expert?.text_expertise_3 || "Text Expertise 3"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Section Témoignages */}
|
{/* Section Témoignages */}
|
||||||
<Box sx={{ py: 6, px: 4 }}>
|
|
||||||
<Typography variant="h2" sx={{ fontWeight: "bold", mb: 2, textAlign: "center", fontSize: { xs: "2rem", md: "2rem", lg: "3rem" } }}>
|
<Typography variant="h2" sx={{ fontWeight: "bold", mb: 2, textAlign: "center", fontSize: { xs: "2rem", md: "2rem", lg: "3rem" } }}>
|
||||||
Ce que disent nos clients
|
Ce que disent nos clients
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={4}>
|
<Testi />
|
||||||
<Grid item xs={12} md={6}>
|
|
||||||
<Card sx={{ boxShadow: 3, padding: 2, backgroundColor: "#f9f9f9" }}>
|
|
||||||
<CardContent>
|
|
||||||
<Typography variant="body1" sx={{ fontStyle: "italic" }}>
|
|
||||||
"Un service exceptionnel, une équipe formidable !"
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" sx={{ mt: 2, textAlign: "right", fontWeight: "bold" }}>
|
|
||||||
- Client 1
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} md={6}>
|
|
||||||
<Card sx={{ boxShadow: 3, padding: 2, backgroundColor: "#f9f9f9" }}>
|
|
||||||
<CardContent>
|
|
||||||
<Typography variant="body1" sx={{ fontStyle: "italic" }}>
|
|
||||||
"Des résultats impressionnants pour notre projet."
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" sx={{ mt: 2, textAlign: "right", fontWeight: "bold" }}>
|
|
||||||
- Client 2
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Section Action */}
|
{/* Section Action */}
|
||||||
<Box sx={{ py: 6, px: 4, backgroundColor: "#0e467f", color: "#fff" }}>
|
<Box sx={{ py: 6, px: 4, backgroundColor: "#0e467f", color: "#fff" }}>
|
||||||
<Typography variant="h2" align="center" gutterBottom sx={{ fontWeight: "bold", mb: 4 }}>
|
<Typography variant="h2" align="center" gutterBottom sx={{ fontWeight: "bold", mb: 4 }}>
|
||||||
@@ -157,10 +134,43 @@ const Home = () => {
|
|||||||
}}
|
}}
|
||||||
href="/contact"
|
href="/contact"
|
||||||
>
|
>
|
||||||
Contactez-nous dès aujourd'hui
|
Contactez-nous
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Section Témoignages */}
|
||||||
|
<Box sx={{ py: 6, px: 4 }}>
|
||||||
|
<Typography variant="h2" sx={{ fontWeight: "bold", mb: 2, textAlign: "center", fontSize: { xs: "2rem", md: "2rem", lg: "3rem" } }}>
|
||||||
|
Nos partenaires
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={4}>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Card sx={{ boxShadow: 3, padding: 2, backgroundColor: "#f9f9f9" }}>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="body1" sx={{ fontStyle: "italic" }}>
|
||||||
|
Un service exceptionnel, une équipe formidable
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ mt: 2, textAlign: "right", fontWeight: "bold" }}>
|
||||||
|
- Client 1
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Card sx={{ boxShadow: 3, padding: 2, backgroundColor: "#f9f9f9" }}>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="body1" sx={{ fontStyle: "italic" }}>
|
||||||
|
Des résultats impressionnants pour notre projet.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ mt: 2, textAlign: "right", fontWeight: "bold" }}>
|
||||||
|
- Client 2
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { loginWithAppPassword, getToken, logout } from "../../auth";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
Container,
|
||||||
|
Avatar,
|
||||||
|
} from "@mui/material";
|
||||||
|
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||||
|
|
||||||
|
function TestLogin() {
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [appPassword, setAppPassword] = useState("");
|
||||||
|
const [token, setToken] = useState(getToken());
|
||||||
|
|
||||||
|
const handleLogin = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const newToken = await loginWithAppPassword(username, appPassword);
|
||||||
|
if (newToken) {
|
||||||
|
setToken(newToken);
|
||||||
|
// alert("Connexion réussie !");
|
||||||
|
} else {
|
||||||
|
alert("Échec de la connexion !");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
logout();
|
||||||
|
setToken(null);
|
||||||
|
// alert("Déconnexion réussie !");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundImage: "url('https://source.unsplash.com/random/1600x900?technology')",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
minHeight: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container maxWidth="xs">
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
padding: 4,
|
||||||
|
boxShadow: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
textAlign: "center",
|
||||||
|
backdropFilter: "blur(10px)",
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<Avatar sx={{ margin: "auto", backgroundColor: "#0e467f" }}>
|
||||||
|
<LockOutlinedIcon />
|
||||||
|
</Avatar>
|
||||||
|
<Typography variant="h5" sx={{ fontWeight: "bold", mt: 2 }}>
|
||||||
|
{token ? "Bienvenue !" : "Connexion"}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{token ? (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
onClick={handleLogout}
|
||||||
|
sx={{ mt: 3, width: "100%" }}
|
||||||
|
>
|
||||||
|
Déconnexion
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={handleLogin}>
|
||||||
|
<TextField
|
||||||
|
label="Nom d'utilisateur"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Mot de passe d'application"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
type="password"
|
||||||
|
value={appPassword}
|
||||||
|
onChange={(e) => setAppPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
sx={{ mt: 3, width: "100%" }}
|
||||||
|
>
|
||||||
|
Se connecter
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TestLogin;
|
||||||
@@ -1,57 +1,78 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState, useEffect } from "react";
|
||||||
import { Grid, Card, CardContent, CardMedia, Typography, Box } from '@mui/material';
|
import { Box, Typography, Grid, Card, CardContent, CardMedia, Button } from "@mui/material";
|
||||||
import api from '../../api'; // Adaptez le chemin vers l'instance Axios
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import api from "../../api";
|
||||||
|
|
||||||
|
const Posts = () => {
|
||||||
|
|
||||||
const Post = () => {
|
|
||||||
const [posts, setPosts] = useState([]);
|
const [posts, setPosts] = useState([]);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.get('wp/v2/posts?_embed')
|
const fetchPosts = async () => {
|
||||||
.then((response) => {
|
try {
|
||||||
setPosts(response.data);
|
const response = await api.get("wp/v2/posts?_fields=id,title,excerpt,featured_media");
|
||||||
|
const postsData = response.data;
|
||||||
|
|
||||||
|
// Récupérer les images en vedette
|
||||||
|
const postsWithImages = await Promise.all(
|
||||||
|
postsData.map(async (post) => {
|
||||||
|
if (post.featured_media) {
|
||||||
|
try {
|
||||||
|
const mediaResponse = await api.get(`wp/v2/media/${post.featured_media}`);
|
||||||
|
return { ...post, image: mediaResponse.data.source_url };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erreur lors de la récupération de l'image pour l'article ${post.id}:`, error);
|
||||||
|
return { ...post, image: null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { ...post, image: null };
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
);
|
||||||
console.error('Erreur lors de la récupération des articles :', error);
|
|
||||||
});
|
setPosts(postsWithImages);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération des articles :", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchPosts();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
// Fonction pour tronquer le texte à 70 caractères
|
||||||
|
const truncateText = (text, maxLength) => {
|
||||||
|
const strippedText = text.replace(/(<([^>]+)>)/gi, ""); // Suppression des balises HTML
|
||||||
|
return strippedText.length > maxLength ? `${strippedText.substring(0, maxLength)}...` : strippedText;
|
||||||
|
};
|
||||||
|
|
||||||
<Box sx={{ padding: 4 }}>
|
return (
|
||||||
<Typography variant="h3" sx={{ marginBottom: 4, textAlign: 'center' }}>
|
<Box sx={{ padding: "40px 20px" }}>
|
||||||
Nos réalisations
|
<Typography variant="h2" sx={{ fontWeight: "bold", textAlign: "center", mb: 4 }}>
|
||||||
|
Nos Articles
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={4}>
|
|
||||||
|
<Grid container spacing={4} justifyContent="center">
|
||||||
{posts.map((post) => (
|
{posts.map((post) => (
|
||||||
<Grid item xs={12} sm={6} md={4} key={post.id}>
|
<Grid item xs={12} sm={6} md={4} key={post.id}>
|
||||||
<Card sx={{ maxWidth: 345,
|
<Card
|
||||||
boxShadow: 3,
|
sx={{
|
||||||
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
|
cursor: "pointer",
|
||||||
'&:hover': {
|
"&:hover": { backgroundColor: "#0e467f", color: "white", transform: "scale(1.05)", boxShadow: 6 },
|
||||||
transform: 'scale(1.05)',
|
}}
|
||||||
boxShadow: 6,
|
onClick={() => navigate(`/post/${post.id}`)}
|
||||||
},
|
>
|
||||||
}}>
|
{post.image && (
|
||||||
{/* Image de l'article */}
|
<CardMedia component="img" height="200" image={post.image} alt={post.title.rendered} />
|
||||||
{post._embedded?.['wp:featuredmedia']?.[0]?.source_url && (
|
|
||||||
<CardMedia
|
|
||||||
component="img"
|
|
||||||
height="200"
|
|
||||||
image={post._embedded['wp:featuredmedia'][0].source_url}
|
|
||||||
alt={post.title.rendered}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h5" component="div" gutterBottom>
|
<Typography variant="h5" sx={{ fontWeight: "bold" }}>
|
||||||
{post.title.rendered}
|
{post.title.rendered}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography variant="body2" sx={{ mt: 1 }}>
|
||||||
variant="body2"
|
{truncateText(post.excerpt.rendered, 100)}
|
||||||
color="text.secondary"
|
</Typography>
|
||||||
dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }}
|
<Button variant="outlined" sx={{ mt: 2, color: "white", borderColor: "white" }}>
|
||||||
/>
|
Lire plus
|
||||||
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -61,4 +82,4 @@ const Post = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Post;
|
export default Posts;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { fetchPosts, deletePost } from "../../wordpress";
|
||||||
|
|
||||||
|
function PostList() {
|
||||||
|
const [posts, setPosts] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function loadPosts() {
|
||||||
|
const data = await fetchPosts();
|
||||||
|
setPosts(data);
|
||||||
|
}
|
||||||
|
loadPosts();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="post-list">
|
||||||
|
<h2>Liste des Articles</h2>
|
||||||
|
{posts.map((post) => (
|
||||||
|
<div key={post.id} className="post-item">
|
||||||
|
<h3>{post.title.rendered}</h3>
|
||||||
|
<button onClick={() => deletePost(post.id)}>Supprimer</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default PostList;
|
||||||
@@ -3,7 +3,7 @@ import ServicePageTemplate from '../ServicePageTemplate';
|
|||||||
|
|
||||||
const ServiceDeux = () => {
|
const ServiceDeux = () => {
|
||||||
const serviceDetails = {
|
const serviceDetails = {
|
||||||
title: 'Formation IA',
|
title: 'Structure béton charpente métallique & bois',
|
||||||
subtitle: 'Dominez l’intelligence artificielle.',
|
subtitle: 'Dominez l’intelligence artificielle.',
|
||||||
description:
|
description:
|
||||||
'Plongez dans le futur ! Notre formation IA vous prépare aux outils modernes d’intelligence artificielle comme Python, TensorFlow et OpenAI API. Apprenez à créer des modèles IA performants.',
|
'Plongez dans le futur ! Notre formation IA vous prépare aux outils modernes d’intelligence artificielle comme Python, TensorFlow et OpenAI API. Apprenez à créer des modèles IA performants.',
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Box, Typography, Button, CircularProgress } from "@mui/material";
|
||||||
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
|
import api from "../api";
|
||||||
|
|
||||||
|
const PostDetails = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const [post, setPost] = useState(null);
|
||||||
|
const [image, setImage] = useState(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPost = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`wp/v2/posts/${id}?_fields=title,content,featured_media,date`);
|
||||||
|
setPost(response.data);
|
||||||
|
|
||||||
|
// Récupération de l'image
|
||||||
|
if (response.data.featured_media) {
|
||||||
|
const mediaResponse = await api.get(`wp/v2/media/${response.data.featured_media}`);
|
||||||
|
setImage(mediaResponse.data.source_url);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération de l'article :", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchPost();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return <CircularProgress sx={{ display: "block", margin: "auto", mt: 5 }} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ padding: "40px 20px",display:"flex", flexDirection:"column" }}>
|
||||||
|
<Button onClick={() => navigate(-1)} variant="outlined" sx={{ mt:5, mb: 3, maxWidth:"200px" }}>
|
||||||
|
⬅ Retour
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Typography variant="h1" sx={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
mb: 4,
|
||||||
|
fontSize: { xs: '3rem', md: '3rem', lg: '3rem' }, // Responsive
|
||||||
|
}}>
|
||||||
|
{post.title.rendered}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ display:"flex", alignItems:"center", justifyContent:"center", padding: "40px 20px",margin:"auto" }}>
|
||||||
|
{image && (
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src={image}
|
||||||
|
alt={post.title.rendered}
|
||||||
|
sx={{ width: "33%", maxHeight: "400px", objectFit: "cover", borderRadius: "8px", mb: 3 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
<Typography variant="body1" sx={{ maxWidth: "800px", mx: "auto" }} dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PostDetails;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { uploadImage, createPost } from "../wordpress";
|
||||||
|
|
||||||
|
function PostForm() {
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [content, setContent] = useState("");
|
||||||
|
const [file, setFile] = useState(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
setError("");
|
||||||
|
|
||||||
|
// 1️⃣ Vérification des champs
|
||||||
|
if (!title.trim() || !content.trim() || !file) {
|
||||||
|
setError("Tous les champs sont requis !");
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2️⃣ Upload de l’image
|
||||||
|
const mediaId = await uploadImage(file);
|
||||||
|
if (!mediaId) {
|
||||||
|
setError("Échec de l'upload de l'image !");
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Création de l’article
|
||||||
|
const success = await createPost(title, content, mediaId);
|
||||||
|
if (success) {
|
||||||
|
alert("Article publié avec succès !");
|
||||||
|
setTitle("");
|
||||||
|
setContent("");
|
||||||
|
setFile(null);
|
||||||
|
} else {
|
||||||
|
setError("Échec de la publication !");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Erreur lors du traitement :", err);
|
||||||
|
setError("Une erreur est survenue, veuillez réessayer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Créer un nouvel article</h2>
|
||||||
|
{error && <p style={{ color: "red" }}>{error}</p>}
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Titre"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
placeholder="Contenu"
|
||||||
|
value={content}
|
||||||
|
onChange={(e) => setContent(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(e) => setFile(e.target.files[0])}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button type="submit" disabled={isLoading}>
|
||||||
|
{isLoading ? "Publication..." : "Publier"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PostForm;
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
const SwitchControl = () => {
|
|
||||||
const [isActive, setIsActive] = useState(() => {
|
|
||||||
return localStorage.getItem("criticalAlertActive") === "yes";
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const alertStatus = localStorage.getItem("criticalAlertActive");
|
|
||||||
setIsActive(alertStatus === "yes");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const toggleAlert = () => {
|
|
||||||
if (!isActive) {
|
|
||||||
localStorage.setItem("criticalAlertActive", "yes");
|
|
||||||
localStorage.setItem("criticalAlertStartTime", Date.now().toString());
|
|
||||||
} else {
|
|
||||||
localStorage.setItem("criticalAlertActive", "no");
|
|
||||||
localStorage.removeItem("criticalAlertStartTime");
|
|
||||||
}
|
|
||||||
setIsActive(!isActive);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="switch-container">
|
|
||||||
<label className="switch">
|
|
||||||
<input type="checkbox" checked={isActive} onChange={toggleAlert} />
|
|
||||||
<span className="slider"></span>
|
|
||||||
</label>
|
|
||||||
<span className={`status-indicator ${isActive ? "active" : "inactive"}`}>
|
|
||||||
{isActive ? "Module Actif" : "Module Inactif"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { loginWithAppPassword, getToken, logout } from "../auth";
|
||||||
|
|
||||||
|
function TestLogin() {
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [appPassword, setAppPassword] = useState("");
|
||||||
|
const [token, setToken] = useState(getToken());
|
||||||
|
|
||||||
|
const handleLogin = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const newToken = await loginWithAppPassword(username, appPassword);
|
||||||
|
if (newToken) {
|
||||||
|
setToken(newToken);
|
||||||
|
alert("Connexion réussie !");
|
||||||
|
} else {
|
||||||
|
alert("Échec de la connexion !");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
logout();
|
||||||
|
setToken(null);
|
||||||
|
alert("Déconnexion réussie !");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Connexion</h2>
|
||||||
|
{token ? (
|
||||||
|
<div>
|
||||||
|
<p>✅ Connecté avec le token : {token}</p>
|
||||||
|
<button onClick={handleLogout}>Déconnexion</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={handleLogin}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Nom d'utilisateur"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Mot de passe d'application"
|
||||||
|
value={appPassword}
|
||||||
|
onChange={(e) => setAppPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button type="submit">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TestLogin;
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Box, Typography, Grid, Avatar } from "@mui/material";
|
||||||
|
|
||||||
|
const TestimonialSection = () => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: "50px 20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* Image à gauche */}
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src="https://picsum.photos/600/600.webp"
|
||||||
|
alt="Illustration"
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
borderRadius: { xs: "15px 15px 0 0", md: "15px 0 0 15px" },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Contenu Témoignage */}
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
xs={12}
|
||||||
|
md={6}
|
||||||
|
sx={{
|
||||||
|
background: "linear-gradient(to right, #002F6C, #0E467F)",
|
||||||
|
color: "white",
|
||||||
|
padding: "40px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
mb: 2,
|
||||||
|
fontStyle: "italic",
|
||||||
|
position: "relative",
|
||||||
|
"&::before": {
|
||||||
|
content: '"“"',
|
||||||
|
fontSize: "50px",
|
||||||
|
position: "absolute",
|
||||||
|
top: "-10px",
|
||||||
|
left: "-5px",
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
In3 a transformé notre vision en réalité avec une approche
|
||||||
|
passionnée et professionnelle.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", mt: 3 }}>
|
||||||
|
<Avatar
|
||||||
|
alt="Marie Dupont"
|
||||||
|
src="https://picsum.photos/100.webp"
|
||||||
|
sx={{ width: 56, height: 56, marginRight: 2 }}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h6" sx={{ fontWeight: "bold" }}>
|
||||||
|
Marie Dupont
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Directrice Créative chez TechVision
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestimonialSection;
|
||||||
@@ -5,17 +5,16 @@ import { ThemeProvider } from "@mui/material/styles";
|
|||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
import { HelmetProvider } from "react-helmet-async";
|
import { HelmetProvider } from "react-helmet-async";
|
||||||
import SimpleReactLightbox from "simple-react-lightbox";
|
import SimpleReactLightbox from "simple-react-lightbox";
|
||||||
import CriticalAlert from "./components/CriticalAlert";
|
import NotFound from "./components/Pages/NotFound.jsx";
|
||||||
import AdminDashboard from "./components/Pages/AdminDashboard";
|
|
||||||
import Backtime from "./components/Pages/Backtime";
|
|
||||||
import NotFound from "./components/Pages/NotFound";
|
|
||||||
|
|
||||||
// Lazy loading des pages
|
// Lazy loading des pages
|
||||||
const Home = lazy(() => import('./components/Pages/Home.jsx'));
|
const Home = lazy(() => import('./components/Pages/Home.jsx'));
|
||||||
const About = lazy(() => import('./components/Pages/About'));
|
const About = lazy(() => import('./components/Pages/About'));
|
||||||
const Contact = lazy(() => import('./components/Pages/Contact'));
|
const Contact = lazy(() => import('./components/Pages/Contact'));
|
||||||
const Post = lazy(() => import('./components/Pages/Post'));
|
const Post = lazy(() => import('./components/Pages/Post'));
|
||||||
|
const PostDetails = lazy(() => import('./components/PostDetails')) ;
|
||||||
const Search = lazy(() => import('./components/Pages/Search')); // Nouveau composant pour la recherche
|
const Search = lazy(() => import('./components/Pages/Search')); // Nouveau composant pour la recherche
|
||||||
|
const BureauEtude = lazy(() => import('./components/Pages/BureauEtude'));
|
||||||
|
|
||||||
// Import des services
|
// Import des services
|
||||||
import ServiceUn from "./components/Pages/ServiceUn.jsx";
|
import ServiceUn from "./components/Pages/ServiceUn.jsx";
|
||||||
@@ -31,19 +30,18 @@ function YourApp() {
|
|||||||
<Suspense fallback={<div>Chargement...</div>}>
|
<Suspense fallback={<div>Chargement...</div>}>
|
||||||
<Router>
|
<Router>
|
||||||
<Header />
|
<Header />
|
||||||
<CriticalAlert />
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/posts" element={<Post />} />
|
<Route path="/posts" element={<Post />} />
|
||||||
|
<Route path="/post/:id" element={<PostDetails />} />
|
||||||
|
<Route path="/bureauEtude" element={<BureauEtude />} />
|
||||||
<Route path="/about" element={<About />} />
|
<Route path="/about" element={<About />} />
|
||||||
<Route path="/contact" element={<Contact />} />
|
<Route path="/contact" element={<Contact />} />
|
||||||
<Route path="/search" element={<Search />} /> {/* Route pour les recherches */}
|
<Route path="/search" element={<Search />} /> {/* Route pour les recherches */}
|
||||||
{/* Routes pour les services */}
|
{/* Routes pour les services */}
|
||||||
<Route path="/services/prestation-maitrise-oeuvre" element={<ServiceUn />} />
|
<Route path="/services/prestation-maitrise-oeuvre" element={<ServiceUn />} />
|
||||||
<Route path="/services/formation-ia" element={<ServiceDeux />} />
|
<Route path="/services/structure-beton-charpente-metallique-bois" element={<ServiceDeux />} />
|
||||||
<Route path="/services/formation-video" element={<ServiceTrois />} />
|
<Route path="/services/formation-video" element={<ServiceTrois />} />
|
||||||
<Route path="/backtime" element={<Backtime />} />
|
|
||||||
<Route path="/admin" element={<AdminDashboard />} />
|
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
<Footer />
|
<Footer />
|
||||||
@@ -52,7 +50,6 @@ function YourApp() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { getToken } from "./auth"; // 🔥 Import du token depuis auth.js
|
||||||
|
|
||||||
|
const API_URL = "https://preprod.octopusdesign.fr/api-octopus/server/wp-json/wp/v2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔹 Upload une image et retourne son ID
|
||||||
|
* @param {File} file - Le fichier image à uploader
|
||||||
|
* @returns {Promise<number>} - L'ID de l'image uploadée
|
||||||
|
*/
|
||||||
|
export async function uploadImage(file) {
|
||||||
|
const token = getToken(); // 🔥 Récupération automatique du token
|
||||||
|
if (!token) {
|
||||||
|
console.error("❌ Aucun token trouvé ! L'utilisateur doit se reconnecter.");
|
||||||
|
throw new Error("Utilisateur non authentifié.");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("📢 Token utilisé pour l'upload :", token);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
formData.append("title", "Nouvelle image");
|
||||||
|
formData.append("status", "publish");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${API_URL}/media`,
|
||||||
|
formData,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Basic ${token}`, // 🔥 Authentification Basic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("✅ Image uploadée avec succès :", response.data);
|
||||||
|
return response.data.id; // Retourne l'ID de l'image
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Erreur d'upload :", error.response ? error.response.data : error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔹 Crée un post WordPress avec une image mise en avant
|
||||||
|
* @param {string} title - Le titre du post
|
||||||
|
* @param {string} content - Le contenu du post
|
||||||
|
* @param {number} imageId - L'ID de l'image à mettre en avant
|
||||||
|
* @returns {Promise<Object>} - Le post créé
|
||||||
|
*/
|
||||||
|
export async function createPost(title, content, imageId) {
|
||||||
|
const token = getToken(); // 🔥 Récupération automatique du token
|
||||||
|
if (!token) {
|
||||||
|
console.error("❌ Aucun token trouvé ! L'utilisateur doit se reconnecter.");
|
||||||
|
throw new Error("Utilisateur non authentifié.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${API_URL}/posts`,
|
||||||
|
{
|
||||||
|
title: title,
|
||||||
|
content: content,
|
||||||
|
status: "publish",
|
||||||
|
featured_media: imageId, // 🔥 Associer l'image mise en avant
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Basic ${token}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("✅ Post créé avec succès :", response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Erreur lors de la création du post :", error.response ? error.response.data : error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -183,3 +183,90 @@ function ajouter_opengraph_meta_api($data, $post, $context) {
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
add_filter('rest_prepare_post', 'ajouter_opengraph_meta_api', 10, 3);
|
add_filter('rest_prepare_post', 'ajouter_opengraph_meta_api', 10, 3);
|
||||||
|
|
||||||
|
// ✅ Active les erreurs PHP pour le debug (désactive après les tests)
|
||||||
|
define('WP_DEBUG', true);
|
||||||
|
define('WP_DEBUG_LOG', true);
|
||||||
|
define('WP_DEBUG_DISPLAY', false);
|
||||||
|
@ini_set('log_errors', 1);
|
||||||
|
@ini_set('display_errors', 0);
|
||||||
|
|
||||||
|
// ✅ Charger la librairie JWT
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
use Firebase\JWT\Key;
|
||||||
|
|
||||||
|
// ✅ Configurer le secret JWT dans `wp-config.php`
|
||||||
|
define('JWT_AUTH_SECRET_KEY', 'change_this_secret_key'); // Change ce secret !
|
||||||
|
|
||||||
|
// ✅ Fonction pour générer un token JWT
|
||||||
|
function custom_generate_jwt_token($user_id) {
|
||||||
|
$issuedAt = time();
|
||||||
|
$expiration = $issuedAt + (60 * 60 * 24); // Expire en 24h
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'iss' => get_bloginfo('url'),
|
||||||
|
'iat' => $issuedAt,
|
||||||
|
'exp' => $expiration,
|
||||||
|
'user_id' => $user_id
|
||||||
|
];
|
||||||
|
|
||||||
|
return JWT::encode($payload, JWT_AUTH_SECRET_KEY, 'HS256');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Fonction pour vérifier un token JWT
|
||||||
|
function custom_authenticate_with_jwt($token) {
|
||||||
|
try {
|
||||||
|
$decoded = JWT::decode($token, new Key(JWT_AUTH_SECRET_KEY, 'HS256'));
|
||||||
|
return get_user_by('ID', $decoded->user_id);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Route API REST pour la connexion
|
||||||
|
function custom_authenticate_user(WP_REST_Request $request) {
|
||||||
|
$parameters = $request->get_json_params();
|
||||||
|
$username = sanitize_text_field($parameters['username']);
|
||||||
|
$password = sanitize_text_field($parameters['password']);
|
||||||
|
|
||||||
|
$user = wp_authenticate($username, $password);
|
||||||
|
|
||||||
|
if (is_wp_error($user)) {
|
||||||
|
return new WP_Error('authentication_failed', 'Identifiants incorrects.', ['status' => 401]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = custom_generate_jwt_token($user->ID);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'user_id' => $user->ID,
|
||||||
|
'token' => $token,
|
||||||
|
'email' => $user->user_email,
|
||||||
|
'role' => $user->roles
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Enregistrement des routes API
|
||||||
|
add_action('rest_api_init', function () {
|
||||||
|
register_rest_route('custom/v1', '/login', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => 'custom_authenticate_user',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Autoriser les uploads et publications via l'API REST
|
||||||
|
function allow_admin_upload_and_post($allcaps, $cap, $args) {
|
||||||
|
if (in_array($cap[0], ['upload_files', 'edit_posts', 'publish_posts'])) {
|
||||||
|
$allcaps[$cap[0]] = true;
|
||||||
|
}
|
||||||
|
return $allcaps;
|
||||||
|
}
|
||||||
|
add_filter('map_meta_cap', 'allow_admin_upload_and_post', 10, 3);
|
||||||
|
|
||||||
|
// ✅ Autoriser CORS (utile pour React)
|
||||||
|
function custom_cors_headers() {
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
|
||||||
|
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||||
|
}
|
||||||
|
add_action('init', 'custom_cors_headers');
|
||||||
Reference in New Issue
Block a user