Maj gestion des articles
This commit is contained in:
@@ -1,54 +1,133 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { uploadImage, createPost } from "../../wordpress";
|
||||
import { getToken } from "../../auth";
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
Paper,
|
||||
Input,
|
||||
IconButton,
|
||||
} from "@mui/material";
|
||||
import { ArrowBack, Publish } from "@mui/icons-material";
|
||||
|
||||
function CreatePost() {
|
||||
const [title, setTitle] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [file, setFile] = useState(null);
|
||||
const [title, setTitle] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [file, setFile] = useState(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
// ✅ Vérification de l'authentification : Redirection vers /login si l'utilisateur n'est pas connecté
|
||||
useEffect(() => {
|
||||
if (!getToken()) {
|
||||
navigate("/admin/login");
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
try {
|
||||
console.log("📢 Tentative d'upload de l'image...");
|
||||
const imageId = await uploadImage(file);
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
console.log("📢 Création du post avec l'image mise en avant...");
|
||||
await createPost(title, content, imageId);
|
||||
try {
|
||||
console.log("📢 Tentative d'upload de l'image...");
|
||||
const imageId = await uploadImage(file);
|
||||
|
||||
alert("✅ Post créé avec succès !");
|
||||
} catch (error) {
|
||||
alert("❌ Erreur lors de la création du post.");
|
||||
}
|
||||
};
|
||||
console.log("📢 Création du post avec l'image mise en avant...");
|
||||
await createPost(title, content, imageId);
|
||||
|
||||
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>
|
||||
);
|
||||
alert("✅ Post créé avec succès !");
|
||||
navigate("/admin/gestion-articles"); // Redirection après la création
|
||||
} catch (error) {
|
||||
alert("❌ Erreur lors de la création du post.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
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">
|
||||
<Paper elevation={6} sx={{ padding: 4, borderRadius: 3 }}>
|
||||
{/* ✅ Bouton Retour à la Gestion des Articles */}
|
||||
<Button
|
||||
startIcon={<ArrowBack />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => navigate("/admin/gestion-articles")}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
Retour à la gestion des articles
|
||||
</Button>
|
||||
|
||||
{/* ✅ Titre de la page */}
|
||||
<Typography variant="h4" sx={{ fontWeight: "bold", textAlign: "center", mb: 3 }}>
|
||||
Créer un article
|
||||
</Typography>
|
||||
|
||||
{/* ✅ Formulaire */}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<TextField
|
||||
label="Titre de l'article"
|
||||
fullWidth
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Contenu"
|
||||
fullWidth
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline
|
||||
rows={4}
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
{/* ✅ Upload de l'image */}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="body1" sx={{ fontWeight: "bold", mb: 1 }}>
|
||||
Image en vedette :
|
||||
</Typography>
|
||||
<Input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => setFile(e.target.files[0])}
|
||||
required
|
||||
sx={{ display: "block", mb: 2 }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* ✅ Bouton Publier */}
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
startIcon={<Publish />}
|
||||
sx={{ mt: 3 }}
|
||||
>
|
||||
Publier l'article
|
||||
</Button>
|
||||
</form>
|
||||
</Paper>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreatePost;
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { getToken, logout } from "../../auth"; // Importation de la fonction logout
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
@@ -6,12 +8,26 @@ import {
|
||||
Card,
|
||||
CardContent,
|
||||
IconButton,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import LogoutIcon from "@mui/icons-material/Logout"; // Icône de déconnexion
|
||||
import ArticleIcon from "@mui/icons-material/Article";
|
||||
import DashboardIcon from "@mui/icons-material/Dashboard";
|
||||
|
||||
function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const token = getToken();
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
navigate("/admin/login"); // Redirige vers la page de connexion si non authentifié
|
||||
}
|
||||
}, [token, navigate]);
|
||||
|
||||
const handleLogout = () => {
|
||||
logout(); // Déconnecte l'utilisateur
|
||||
navigate("/admin/login"); // Redirige vers la page de connexion
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -35,10 +51,30 @@ function Dashboard() {
|
||||
borderRadius: 3,
|
||||
boxShadow: 6,
|
||||
textAlign: "center",
|
||||
position: "relative", // Permet de positionner des éléments enfants
|
||||
}}
|
||||
>
|
||||
{/* Bouton de déconnexion placé en haut à gauche de la boîte blanche */}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
startIcon={<LogoutIcon />}
|
||||
onClick={handleLogout}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 10,
|
||||
left: 10,
|
||||
backgroundColor: "#d32f2f",
|
||||
"&:hover": { backgroundColor: "#b71c1c" },
|
||||
fontSize: "0.875rem",
|
||||
padding: "6px 12px",
|
||||
}}
|
||||
>
|
||||
|
||||
</Button>
|
||||
|
||||
{/* En-tête */}
|
||||
<Typography variant="h4" sx={{ fontWeight: "bold", mb: 4 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: "bold", mb: 4, mt: 4 }}>
|
||||
<DashboardIcon sx={{ fontSize: 40, color: "#0e467f", mr: 1 }} />
|
||||
Tableau de Bord
|
||||
</Typography>
|
||||
@@ -47,7 +83,7 @@ function Dashboard() {
|
||||
<Grid container spacing={3} justifyContent="center">
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<Card
|
||||
onClick={() => navigate("/posts")}
|
||||
onClick={() => navigate("/admin/gestion-articles")}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
textAlign: "center",
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
// ✅ Importations nécessaires
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Box, Typography, Button, Table, TableBody, TableCell,
|
||||
TableContainer, TableHead, TableRow, Paper, TextField, Avatar
|
||||
} from "@mui/material";
|
||||
import { Add, ArrowBack, Edit, Delete } from "@mui/icons-material";
|
||||
import api from "../../api";
|
||||
import { getToken } from "../../auth";
|
||||
|
||||
const GestionArticles = () => {
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
// ✅ Vérifier si l'utilisateur est connecté
|
||||
useEffect(() => {
|
||||
if (!getToken()) {
|
||||
navigate("/admin/login");
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
// ✅ Récupérer les articles et leurs images associées
|
||||
useEffect(() => {
|
||||
const fetchPosts = async () => {
|
||||
try {
|
||||
const response = await api.get("wp/v2/posts?_fields=id,title,excerpt,featured_media");
|
||||
const postsData = response.data;
|
||||
|
||||
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, imageId: post.featured_media };
|
||||
} catch (error) {
|
||||
console.error(`❌ Erreur chargement image article ${post.id}:`, error);
|
||||
return { ...post, image: null, imageId: null };
|
||||
}
|
||||
}
|
||||
return { ...post, image: null, imageId: null };
|
||||
})
|
||||
);
|
||||
|
||||
setPosts(postsWithImages);
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur chargement des articles :", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPosts();
|
||||
}, []);
|
||||
|
||||
// ✅ Supprimer un article et son image associée
|
||||
const handleDeletePost = async (postId, imageId) => {
|
||||
if (!window.confirm("⚠ Es-tu sûr de vouloir supprimer cet article ?")) return;
|
||||
|
||||
try {
|
||||
const token = getToken(); // 🔑 Récupérer le token JWT
|
||||
if (!token) {
|
||||
alert("❌ Erreur : utilisateur non authentifié !");
|
||||
navigate("/admin/login");
|
||||
return;
|
||||
}
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`, // ✅ Ajout du token dans l'en-tête
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
console.log(`📢 Suppression de l'article ID: ${postId}`);
|
||||
|
||||
// ✅ Suppression de l'article (avec force=true)
|
||||
await api.delete(`wp/v2/posts/${postId}?force=true`, { headers });
|
||||
|
||||
// ✅ Suppression de l'image associée si elle existe
|
||||
if (imageId) {
|
||||
console.log(`📢 Suppression de l'image ID: ${imageId}`);
|
||||
await api.delete(`wp/v2/media/${imageId}?force=true`, { headers });
|
||||
}
|
||||
|
||||
// ✅ Mise à jour de la liste après suppression
|
||||
setPosts(posts.filter((post) => post.id !== postId));
|
||||
|
||||
alert("✅ Article supprimé avec succès !");
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur suppression article :", error);
|
||||
alert(`⚠ Erreur : impossible de supprimer l'article.\n\nDétails: ${error.response?.data?.message || error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ Filtrage des articles
|
||||
const filteredPosts = posts.filter((post) =>
|
||||
post.title.rendered.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
post.excerpt.rendered.replace(/(<([^>]+)>)/gi, "").toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ padding: "40px 20px" }}>
|
||||
{/* ✅ En-tête avec retour et création */}
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 4, mt:5 }}>
|
||||
<Button startIcon={<ArrowBack />} variant="outlined" color="secondary" onClick={() => navigate("/admin/dashboard")}>
|
||||
Retour au Dashboard
|
||||
</Button>
|
||||
<Typography variant="h4" sx={{ fontWeight: "bold", textAlign: "center", flexGrow: 1 }}>
|
||||
Gestion des Articles
|
||||
</Typography>
|
||||
<Button startIcon={<Add />} variant="contained" color="primary" onClick={() => navigate("/admin/create-post")}>
|
||||
Créer un article
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* ✅ Recherche */}
|
||||
<TextField
|
||||
label="Rechercher un article..."
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
sx={{ mb: 3 }}
|
||||
/>
|
||||
|
||||
{/* ✅ Tableau des articles */}
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead sx={{ backgroundColor: "#0e467f" }}>
|
||||
<TableRow>
|
||||
<TableCell sx={{ color: "white", fontWeight: "bold" }}>Image</TableCell>
|
||||
<TableCell sx={{ color: "white", fontWeight: "bold" }}>Titre</TableCell>
|
||||
<TableCell sx={{ color: "white", fontWeight: "bold" }}>Résumé</TableCell>
|
||||
<TableCell sx={{ color: "white", fontWeight: "bold", textAlign: "center" }}>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filteredPosts.length > 0 ? (
|
||||
filteredPosts.map((post) => (
|
||||
<TableRow key={post.id}>
|
||||
{/* ✅ Image en vedette */}
|
||||
<TableCell>
|
||||
<Avatar
|
||||
src={post.image || "https://via.placeholder.com/100"}
|
||||
variant="rounded"
|
||||
sx={{ width: 80, height: 80 }}
|
||||
/>
|
||||
</TableCell>
|
||||
{/* ✅ Titre de l'article */}
|
||||
<TableCell sx={{ fontWeight: "bold" }}>{post.title.rendered}</TableCell>
|
||||
{/* ✅ Résumé avec 100 caractères max */}
|
||||
<TableCell>
|
||||
{post.excerpt.rendered.replace(/(<([^>]+)>)/gi, "").substring(0, 100)}...
|
||||
</TableCell>
|
||||
{/* ✅ Boutons Modifier et Supprimer */}
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Button startIcon={<Edit />} variant="outlined" color="warning" sx={{ mr: 1 }} >
|
||||
Modifier
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<Delete />}
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={() => handleDeletePost(post.id, post.imageId)}
|
||||
>
|
||||
Supprimer
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} sx={{ textAlign: "center", py: 2 }}>
|
||||
Aucun article trouvé.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default GestionArticles;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { loginWithAppPassword, getToken, logout } from "../../auth";
|
||||
import { useNavigate } from "react-router-dom"; // Import de useNavigate pour la redirection
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -16,13 +17,13 @@ function TestLogin() {
|
||||
const [username, setUsername] = useState("");
|
||||
const [appPassword, setAppPassword] = useState("");
|
||||
const [token, setToken] = useState(getToken());
|
||||
const navigate = useNavigate(); // Hook pour la navigation
|
||||
|
||||
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 !");
|
||||
}
|
||||
@@ -31,7 +32,6 @@ function TestLogin() {
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
setToken(null);
|
||||
// alert("Déconnexion réussie !");
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -66,14 +66,24 @@ function TestLogin() {
|
||||
</Typography>
|
||||
|
||||
{token ? (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={handleLogout}
|
||||
sx={{ mt: 3, width: "100%" }}
|
||||
>
|
||||
Déconnexion
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => navigate("/admin/dashboard")}
|
||||
sx={{ mt: 3, width: "100%" }}
|
||||
>
|
||||
Accéder au tableau de bord
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={handleLogout}
|
||||
sx={{ mt: 2, width: "100%" }}
|
||||
>
|
||||
Déconnexion
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<form onSubmit={handleLogin}>
|
||||
<TextField
|
||||
|
||||
@@ -1,64 +1,90 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Grid, Card, CardContent, CardMedia, Typography, Box } from '@mui/material';
|
||||
import api from '../../api'; // Adaptez le chemin vers l'instance Axios
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Box, Typography, Grid, Card, CardContent, CardMedia, Button } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import api from "../../api";
|
||||
|
||||
const Posts = () => {
|
||||
const [posts, setPosts] = useState([]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPosts = async () => {
|
||||
try {
|
||||
const response = await api.get("wp/v2/posts?_fields=id,title,excerpt,content,featured_media");
|
||||
const postsData = response.data;
|
||||
|
||||
const Post = () => {
|
||||
const [posts, setPosts] = useState([]);
|
||||
// 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 };
|
||||
})
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
api.get('wp/v2/posts?_embed')
|
||||
.then((response) => {
|
||||
setPosts(response.data);
|
||||
})
|
||||
.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);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<Box sx={{ padding: 4 }}>
|
||||
<Typography variant="h3" sx={{ marginBottom: 4, textAlign: 'center' }}>
|
||||
Nos réalisations
|
||||
</Typography>
|
||||
<Grid container spacing={4}>
|
||||
{posts.map((post) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={post.id}>
|
||||
<Card sx={{ maxWidth: 345,
|
||||
boxShadow: 3,
|
||||
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
|
||||
'&:hover': {
|
||||
transform: 'scale(1.05)',
|
||||
boxShadow: 6,
|
||||
},
|
||||
}}>
|
||||
{/* Image de l'article */}
|
||||
{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>
|
||||
<Typography variant="h5" component="div" gutterBottom>
|
||||
{post.title.rendered}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
fetchPosts();
|
||||
}, []);
|
||||
|
||||
// Fonction pour nettoyer le HTML et récupérer un texte brut
|
||||
const stripHtmlTags = (html) => {
|
||||
return html.replace(/(<([^>]+)>)/gi, ""); // Supprime toutes les balises HTML
|
||||
};
|
||||
|
||||
// Fonction pour tronquer un texte à 100 caractères max
|
||||
const truncateText = (text, maxLength) => {
|
||||
const strippedText = stripHtmlTags(text); // Nettoyage HTML
|
||||
return strippedText.length > maxLength ? `${strippedText.substring(0, maxLength)}...` : strippedText;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ padding: "40px 20px" }}>
|
||||
<Typography variant="h2" sx={{ fontWeight: "bold", textAlign: "center", mb: 4 }}>
|
||||
Nos Articles
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={4} justifyContent="center">
|
||||
{posts.map((post) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={post.id}>
|
||||
<Card
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
"&:hover": { backgroundColor: "#0e467f", color: "white", transform: "scale(1.05)", boxShadow: 6 },
|
||||
}}
|
||||
onClick={() => navigate(`/post/${post.id}`)}
|
||||
>
|
||||
{post.image && (
|
||||
<CardMedia component="img" height="200" image={post.image} alt={post.title.rendered} />
|
||||
)}
|
||||
<CardContent>
|
||||
<Typography variant="h5" sx={{ fontWeight: "bold" }}>
|
||||
{post.title.rendered}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mt: 1 }}>
|
||||
{truncateText(post.excerpt.rendered || post.content.rendered, 100)}
|
||||
</Typography>
|
||||
<Button variant="outlined" sx={{ mt: 2, color: "white", borderColor: "white" }}>
|
||||
Lire plus
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Post;
|
||||
export default Posts;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user