update edit post cloudinary
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user