add function creatPost and login wp
This commit is contained in:
parent
81ac3d831e
commit
841161cb9b
@ -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 /
|
||||
|
||||
811
frontend/package-lock.json
generated
811
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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
35
frontend/src/auth.js
Normal 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 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");
|
||||
};
|
||||
17
frontend/src/components/DeletePost.jsx
Normal file
17
frontend/src/components/DeletePost.jsx
Normal 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;
|
||||
34
frontend/src/components/EditPost.jsx
Normal file
34
frontend/src/components/EditPost.jsx
Normal 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 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;
|
||||
54
frontend/src/components/Pages/CreatePost.jsx
Normal file
54
frontend/src/components/Pages/CreatePost.jsx
Normal 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;
|
||||
83
frontend/src/components/Pages/Dashboard.jsx
Normal file
83
frontend/src/components/Pages/Dashboard.jsx
Normal 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;
|
||||
@ -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
|
||||
|
||||
115
frontend/src/components/Pages/Login.jsx
Normal file
115
frontend/src/components/Pages/Login.jsx
Normal 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;
|
||||
27
frontend/src/components/Pages/PostList.jsx
Normal file
27
frontend/src/components/Pages/PostList.jsx
Normal 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;
|
||||
83
frontend/src/components/PostForm.jsx
Normal file
83
frontend/src/components/PostForm.jsx
Normal 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 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;
|
||||
57
frontend/src/components/TestLogin.jsx
Normal file
57
frontend/src/components/TestLogin.jsx
Normal 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;
|
||||
@ -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
81
frontend/src/wordpress.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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');
|
||||
Loading…
Reference in New Issue
Block a user