This commit is contained in:
@ -5,7 +5,9 @@ import Footer from './components/Footer';
|
||||
import Home from './pages/Home';
|
||||
import About from './pages/About';
|
||||
import PostList from './pages/PostList';
|
||||
import PostDetail from './pages/PostDetail';
|
||||
import PostCreate from './pages/PostCreate';
|
||||
import PostEdit from './pages/PostEdit';
|
||||
import PostCategory from './pages/PostCategory';
|
||||
import Login from './pages/Login';
|
||||
import { AuthProvider } from './context/AuthContext';
|
||||
@ -21,7 +23,9 @@ function App() {
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/posts" element={<PostList />} />
|
||||
<Route path="/posts/:id" element={<PostDetail />} />
|
||||
<Route path="/postCreate" element={<PostCreate />} />
|
||||
<Route path="/posts/:id/edit" element={<PostEdit />} />
|
||||
<Route path="/post/:category" element={<PostCategory />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
</Routes>
|
||||
|
66
src/pages/PostDetail.js
Normal file
66
src/pages/PostDetail.js
Normal file
@ -0,0 +1,66 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import blogApi from '../api/blogApi';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
|
||||
const PostDetail = () => {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { isLoggedIn } = useAuth();
|
||||
const [post, setPost] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
blogApi.get(`/api/blog/posts/${id}/`)
|
||||
.then(res => setPost(res.data))
|
||||
.catch(err => console.error('게시글 조회 실패:', err));
|
||||
}, [id]);
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!window.confirm('정말 삭제하시겠습니까?')) return;
|
||||
|
||||
try {
|
||||
await blogApi.delete(`/api/blog/posts/${id}/`);
|
||||
alert('게시글이 삭제되었습니다.');
|
||||
navigate('/posts');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
if (!post) return <div className="p-8">Loading...</div>;
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto p-8">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h1 className="text-3xl font-bold">{post.title}</h1>
|
||||
{isLoggedIn && (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => navigate(`/posts/${id}/edit`)}
|
||||
className="bg-yellow-500 text-white px-4 py-2 rounded hover:bg-yellow-600"
|
||||
>
|
||||
수정
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600"
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<p className="text-gray-600 mb-2">작성자: {post.author_name}</p>
|
||||
<p className="text-gray-400 text-sm mb-6">
|
||||
작성일: {new Date(post.created_at).toLocaleString()}
|
||||
</p>
|
||||
<div className="bg-white p-6 rounded shadow text-gray-800 leading-relaxed whitespace-pre-line">
|
||||
{post.content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostDetail;
|
70
src/pages/PostEdit.js
Normal file
70
src/pages/PostEdit.js
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import blogApi from '../api/blogApi';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
|
||||
const PostEdit = () => {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { isLoggedIn } = useAuth();
|
||||
const [title, setTitle] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
blogApi.get(`/api/blog/posts/${id}/`)
|
||||
.then(res => {
|
||||
setTitle(res.data.title);
|
||||
setContent(res.data.content);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('게시글 불러오기 실패:', err);
|
||||
alert('게시글을 불러오지 못했습니다.');
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await blogApi.put(`/api/blog/posts/${id}/`, { title, content });
|
||||
alert('게시글이 수정되었습니다.');
|
||||
navigate(`/posts/${id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('수정 실패: ' + (err.response?.data?.detail || '서버 오류'));
|
||||
}
|
||||
};
|
||||
|
||||
if (!isLoggedIn) return <div className="p-8">로그인이 필요합니다.</div>;
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto p-8">
|
||||
<h2 className="text-2xl font-bold mb-6">게시글 수정</h2>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full border px-4 py-2 mb-4 rounded"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
<textarea
|
||||
className="w-full border px-4 py-2 h-48 mb-4 rounded"
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
></textarea>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className="bg-[#3B82F6] text-white px-6 py-2 rounded hover:bg-blue-700"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className="bg-gray-300 text-gray-800 px-6 py-2 rounded hover:bg-gray-400"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostEdit;
|
@ -31,7 +31,11 @@ const PostList = () => {
|
||||
|
||||
<ul className="space-y-4">
|
||||
{posts.map(post => (
|
||||
<li key={post.id} className="p-4 border rounded shadow-sm bg-white">
|
||||
<li
|
||||
key={post.id}
|
||||
className="p-4 border rounded shadow-sm bg-white hover:bg-gray-50 cursor-pointer"
|
||||
onClick={() => navigate(`/posts/${post.id}`)}
|
||||
>
|
||||
<h2 className="text-xl font-semibold">{post.title}</h2>
|
||||
<p className="text-gray-600">작성자: {post.author_name}</p>
|
||||
<p className="text-gray-400 text-sm">
|
||||
|
Reference in New Issue
Block a user