add function creatPost and login wp

This commit is contained in:
sebvtl728 2025-01-31 08:22:48 +01:00
parent 81ac3d831e
commit 841161cb9b
16 changed files with 1177 additions and 355 deletions

View File

@ -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>
RewriteEngine On
RewriteBase /

File diff suppressed because it is too large Load Diff

View File

@ -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({
baseURL: 'https://preprod.octopusdesign.fr/api-octopus/server/wp-json',
withCredentials: true,
baseURL: API_URL,
headers: {
"Content-Type": "application/json"
}
});
// 🔹 Fonction générique pour récupérer le token
export const getToken = () => {
return sessionStorage.getItem("custom_token");
};
// Exportation de l'instance Axios par défaut
export default api;
// 🔹 Exporter l'instance Axios
export default api;

35
frontend/src/auth.js Normal file
View File

@ -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 dapplication
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");
};

View File

@ -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;

View File

@ -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 larticle</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;

View File

@ -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;

View File

@ -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;

View File

@ -1,11 +1,11 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import Hero from "../Hero";
import SEO from "../SEO";
import api from "../../api";
import ModernSpinner from "../SpinnerModerne";
import Expertises from "../Expertises";
import ConstructSection from "../ConstructSection";
import { Box, Grid, Typography, Button, Card, CardContent, Avatar } from "@mui/material";
import { Box, Grid, Typography, Button, Card, CardContent } from "@mui/material";
import Logo from "../../assets/logo-in3-mobil.svg";
import Testi from "../Testi";
@ -134,7 +134,7 @@ const Home = () => {
}}
href="/contact"
>
Contactez-nous dès aujourd'hui
Contactez-nous
</Button>
</Box>
</Box>
@ -149,7 +149,7 @@ const Home = () => {
<Card sx={{ boxShadow: 3, padding: 2, backgroundColor: "#f9f9f9" }}>
<CardContent>
<Typography variant="body1" sx={{ fontStyle: "italic" }}>
"Un service exceptionnel, une équipe formidable !"
Un service exceptionnel, une équipe formidable
</Typography>
<Typography variant="body2" sx={{ mt: 2, textAlign: "right", fontWeight: "bold" }}>
- Client 1
@ -161,7 +161,7 @@ const Home = () => {
<Card sx={{ boxShadow: 3, padding: 2, backgroundColor: "#f9f9f9" }}>
<CardContent>
<Typography variant="body1" sx={{ fontStyle: "italic" }}>
"Des résultats impressionnants pour notre projet."
Des résultats impressionnants pour notre projet.
</Typography>
<Typography variant="body2" sx={{ mt: 2, textAlign: "right", fontWeight: "bold" }}>
- Client 2

View File

@ -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;

View File

@ -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;

View File

@ -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 limage
const mediaId = await uploadImage(file);
if (!mediaId) {
setError("Échec de l'upload de l'image !");
setIsLoading(false);
return;
}
// 3 Création de larticle
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;

View File

@ -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;

View File

@ -7,6 +7,7 @@ import { HelmetProvider } from "react-helmet-async";
import SimpleReactLightbox from "simple-react-lightbox";
import NotFound from "./components/Pages/NotFound.jsx";
// Lazy loading des pages
const Home = lazy(() => import('./components/Pages/Home.jsx'));
const About = lazy(() => import('./components/Pages/About'));
@ -14,6 +15,10 @@ const Contact = lazy(() => import('./components/Pages/Contact'));
const Post = lazy(() => import('./components/Pages/Post'));
const Search = lazy(() => import('./components/Pages/Search')); // Nouveau composant pour la recherche
const BureauEtude = lazy(() => import('./components/Pages/BureauEtude'));
const CreatePost = lazy(() => import('./components/Pages/CreatePost'));
const PostList = lazy(() => import("./components/Pages/PostList"));
const Login = lazy(() => import("./components/Pages/Login"));
const Dashboard = lazy(() => import("./components/Pages/Dashboard"));
// Import des services
import ServiceUn from "./components/Pages/ServiceUn.jsx";
@ -40,6 +45,10 @@ function YourApp() {
<Route path="/services/prestation-maitrise-oeuvre" element={<ServiceUn />} />
<Route path="/services/structure-beton-charpente-metallique-bois" element={<ServiceDeux />} />
<Route path="/services/formation-video" element={<ServiceTrois />} />
<Route path="/create-post" element={<CreatePost />} />
<Route path="/admin/posts" element={<PostList />} />
<Route path="/admin/login" element={<Login />} />
<Route path="/admin/dashboard" element={<Dashboard />} />
<Route path="*" element={<NotFound />} />
</Routes>
<Footer />

81
frontend/src/wordpress.js Normal file
View File

@ -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;
}
}

View File

@ -182,4 +182,91 @@ function ajouter_opengraph_meta_api($data, $post, $context) {
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');