update edit post cloudinary

This commit is contained in:
sebvtl728
2025-07-31 18:25:36 +02:00
parent 2ee6c53a98
commit e267591ebb
4 changed files with 131 additions and 140 deletions
Vendored
BIN
View File
Binary file not shown.
+60 -81
View File
@@ -1,129 +1,108 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate } from "react-router-dom";
import { getPostById, updatePost, uploadImage } from "../wordpress"; import { getPostById, updatePost, uploadImageFromUrl } from "../wordpress"; // utilise uploadImageFromUrl
import { getToken } from "../auth"; import { getToken } from "../auth";
import { import {
Box, Container, Typography, TextField, Button, Paper, Input, IconButton Box, Container, Typography, TextField, Button, Paper
} from "@mui/material"; } from "@mui/material";
import { ArrowBack, Publish } from "@mui/icons-material"; import { ArrowBack, Publish } from "@mui/icons-material";
import ImageUploaderCloudinary from "./ImageUploaderCloudinary";
import CloudinaryGallerySelector from "./CloudinaryGallerySelector";
function EditPost() { function EditPost() {
const { id } = useParams(); // ✅ Récupère l'ID de l'article depuis l'URL const { id } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const [content, setContent] = useState(""); const [content, setContent] = useState("");
const [file, setFile] = useState(null); const [imageUrl, setImageUrl] = useState(null); // preview
const [loading, setLoading] = useState(true); const [imageFile, setImageFile] = useState(null); // pour upload
// ✅ Vérification de l'authentification // Auth
useEffect(() => { useEffect(() => {
if (!getToken()) { if (!getToken()) navigate("/admin/login");
navigate("/admin/login");
}
}, [navigate]); }, [navigate]);
// ✅ Récupérer les données de l'article // Article
useEffect(() => { useEffect(() => {
const fetchPost = async () => { const fetchPost = async () => {
try { try {
const post = await getPostById(id); const post = await getPostById(id);
setTitle(post.title.rendered); setTitle(post.title.rendered);
setContent(post.content.rendered.replace(/(<([^>]+)>)/gi, "")); // Enlever le HTML setContent(post.content.rendered.replace(/(<([^>]+)>)/gi, ""));
setLoading(false); setImageUrl(post?.jetpack_featured_media_url || null); // preview actuelle
} catch (error) { } catch (err) {
console.error("Erreur chargement article :", error); console.error("Erreur chargement article :", err);
navigate("/admin/gestion-articles"); // Redirige si erreur navigate("/admin/gestion-articles");
} }
}; };
fetchPost(); fetchPost();
}, [id, navigate]); }, [id, navigate]);
// 🧠 convertit URL en File
const convertUrlToFile = async (url) => {
const res = await fetch(url);
const blob = await res.blob();
return new File([blob], "image-cloudinary.jpg", { type: blob.type });
};
const handleUpdate = async (e) => { const handleUpdate = async (e) => {
e.preventDefault(); e.preventDefault();
try { try {
const updatedPost = await updatePost(id, title, content, file); let imageId = null;
alert("✅ Article mis à jour avec succès !");
navigate("/admin/gestion-articles"); // Redirige après modification if (imageFile) {
// upload image vers WordPress
imageId = await uploadImageFromUrl(imageUrl);
}
await updatePost(id, title, content, imageId);
alert("✅ Article mis à jour !");
navigate("/admin/gestion-articles");
} catch (error) { } catch (error) {
alert("❌ Erreur lors de la mise à jour de l'article."); console.error("❌ Erreur update :", error);
alert("❌ Erreur mise à jour.");
} }
}; };
if (loading) return <Typography>Chargement...</Typography>;
return ( return (
<Box <Box sx={{ minHeight: "100vh", display: "flex", justifyContent: "center", alignItems: "center" }}>
sx={{
minHeight: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundImage: "url('https://source.unsplash.com/1600x900/?office,writing')",
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
<Container maxWidth="sm"> <Container maxWidth="sm">
<Paper elevation={6} sx={{ padding: 4, borderRadius: 3 }}> <Paper elevation={6} sx={{ p: 4, borderRadius: 3 }}>
<Button <Button onClick={() => navigate("/admin/gestion-articles")} variant="outlined" startIcon={<ArrowBack />}>
startIcon={<ArrowBack />} Retour
variant="outlined"
color="secondary"
onClick={() => navigate("/admin/gestion-articles")}
sx={{ mb: 2 }}
>
Retour à la gestion des articles
</Button> </Button>
<Typography variant="h4" sx={{ fontWeight: "bold", textAlign: "center", mb: 3 }}> <Typography variant="h4" align="center" sx={{ mb: 3 }}>Modifier l'article</Typography>
Modifier l'article
</Typography>
<form onSubmit={handleUpdate}> <form onSubmit={handleUpdate}>
<TextField <TextField label="Titre" fullWidth value={title} onChange={(e) => setTitle(e.target.value)} sx={{ mb: 2 }} />
label="Titre de l'article" <TextField label="Contenu" multiline rows={4} fullWidth value={content} onChange={(e) => setContent(e.target.value)} sx={{ mb: 2 }} />
fullWidth
margin="normal" <Typography fontWeight="bold">Image depuis ton ordi :</Typography>
variant="outlined" <ImageUploaderCloudinary
value={title} onUploadSuccess={(url) => {
onChange={(e) => setTitle(e.target.value)} setImageUrl(url);
required convertUrlToFile(url).then(setImageFile);
}}
/> />
<TextField <Typography fontWeight="bold" sx={{ mt: 3 }}>Ou choisir une image déjà envoyée :</Typography>
label="Contenu" <CloudinaryGallerySelector
fullWidth onSelect={async (url) => {
margin="normal" setImageUrl(url);
variant="outlined" const file = await convertUrlToFile(url);
multiline setImageFile(file);
rows={4} }}
value={content}
onChange={(e) => setContent(e.target.value)}
required
/> />
{/* Upload de l'image */} {imageUrl && (
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
<Typography variant="body1" sx={{ fontWeight: "bold", mb: 1 }}> <Typography variant="body2">Aperçu :</Typography>
Nouvelle image (facultatif) : <img src={imageUrl} style={{ width: "100%", borderRadius: 6, maxHeight: 200, objectFit: "cover" }} alt="preview" />
</Typography>
<Input
type="file"
accept="image/*"
onChange={(e) => setFile(e.target.files[0])}
sx={{ display: "block", mb: 2 }}
/>
</Box> </Box>
)}
<Button <Button type="submit" variant="contained" fullWidth sx={{ mt: 3 }} startIcon={<Publish />}>
type="submit"
variant="contained"
color="primary"
fullWidth
startIcon={<Publish />}
sx={{ mt: 3 }}
>
Mettre à jour Mettre à jour
</Button> </Button>
</form> </form>
+35 -24
View File
@@ -131,23 +131,29 @@ export async function getPostById(postId) {
* @param {string} content - Le nouveau contenu * @param {string} content - Le nouveau contenu
* @returns {Promise<Object>} - L'article mis à jour * @returns {Promise<Object>} - L'article mis à jour
*/ */
export async function updatePost(postId, title, content) { export async function updatePost(postId, title, content, imageId = null) {
const token = getToken(); const token = getToken();
if (!token) { if (!token) {
console.error("❌ Aucun token trouvé !"); console.error("❌ Aucun token trouvé !");
throw new Error("Utilisateur non authentifié."); throw new Error("Utilisateur non authentifié.");
} }
try { const dataToSend = {
const response = await axios.post(`${API_URL}/posts/${postId}`, { // ✅ Rester sur `POST`
title, title,
content, content,
status: "publish", status: "publish",
}, { };
headers: {
"Authorization": `Basic ${token}`, // ✅ Garder `Basic` si ça fonctionnait avant if (imageId) {
"Content-Type": "application/json" dataToSend.featured_media = imageId; // 🔥 Ajoute l'image en vedette
} }
try {
const response = await axios.post(`${API_URL}/posts/${postId}`, dataToSend, {
headers: {
Authorization: `Basic ${token}`,
"Content-Type": "application/json",
},
}); });
console.log("✅ Article mis à jour avec succès :", response.data); console.log("✅ Article mis à jour avec succès :", response.data);
@@ -158,49 +164,54 @@ export async function updatePost(postId, title, content) {
} }
} }
/** /**
* 🔹 Supprime un article WordPress et son image associée * 🔹 Supprime un article WordPress et son image associée
* @param {number} postId - L'ID de l'article à supprimer * @param {number} postId - L'ID de l'article à supprimer
* @param {number} imageId - L'ID de l'image en vedette à supprimer (facultatif) * @param {number} imageId - L'ID de l'image en vedette à supprimer (facultatif)
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export async function deletePost(postId, imageId) { export async function deletePost(postId) {
const token = getToken(); const token = getToken();
if (!token) { if (!token) throw new Error("Utilisateur non authentifié.");
console.error("❌ Aucun token trouvé !");
throw new Error("Utilisateur non authentifié.");
}
try { try {
console.log(`📢 Suppression article ID: ${postId}...`); console.log(`📢 Suppression article ID: ${postId}...`);
// ✅ Suppression du post // 🔍 Récupérer les infos de l'article pour connaître l'image
const postRes = await axios.get(`${API_URL}/posts/${postId}`, {
headers: {
Authorization: `Basic ${token}`,
},
});
const imageId = postRes.data.featured_media;
// ✅ Supprimer l'article
await axios.delete(`${API_URL}/posts/${postId}?force=true`, { await axios.delete(`${API_URL}/posts/${postId}?force=true`, {
headers: { headers: {
"Authorization": `Basic ${token}`, // ✅ Garder `Basic` si ça fonctionnait avant Authorization: `Basic ${token}`,
"Content-Type": "application/json" },
}
}); });
console.log(`✅ Article ${postId} supprimé.`); console.log(`✅ Article ${postId} supprimé.`);
// Suppression de l'image associée si elle existe // 🧹 Supprimer l'image si elle existe
if (imageId) { if (imageId) {
console.log(`📢 Suppression image ID: ${imageId}...`);
await axios.delete(`${API_URL}/media/${imageId}?force=true`, { await axios.delete(`${API_URL}/media/${imageId}?force=true`, {
headers: { headers: {
"Authorization": `Basic ${token}`, // ✅ Garder `Basic` si ça fonctionnait avant Authorization: `Basic ${token}`,
"Content-Type": "application/json" },
}
}); });
console.log(` Image ${imageId} supprimée.`); console.log(`🗑️ Image ${imageId} supprimée.`);
} }
} catch (error) { } catch (error) {
console.error("❌ Erreur suppression :", error.response?.data || error.message); console.error("❌ Erreur suppression article ou image :", error.response?.data || error.message);
throw error; throw error;
} }
} }
/** /**
* 🔹 Met à jour les champs ACF d'une page WordPress * 🔹 Met à jour les champs ACF d'une page WordPress
* @param {number} pageId - L'ID de la page à modifier * @param {number} pageId - L'ID de la page à modifier
+2 -1
View File
@@ -7,7 +7,8 @@ dotenv.config();
const app = express(); const app = express();
app.use(cors({ app.use(cors({
origin: "https://octopusdesign.fr" // origin: "https://octopusdesign.fr"
origin: ["https://octopusdesign.fr", "http://localhost:3000"]
})); }));
app.use("/api", cloudinaryRoute); app.use("/api", cloudinaryRoute);