update
All checks were successful
Build And Test / build-and-push (push) Successful in 1m32s

This commit is contained in:
2025-04-24 11:21:23 +09:00
parent 293bcdbea6
commit 35c46cc50e
5 changed files with 146 additions and 2 deletions

View File

@ -5,7 +5,9 @@ import Footer from './components/Footer';
import Home from './pages/Home'; import Home from './pages/Home';
import About from './pages/About'; import About from './pages/About';
import PostList from './pages/PostList'; import PostList from './pages/PostList';
import PostDetail from './pages/PostDetail';
import PostCreate from './pages/PostCreate'; import PostCreate from './pages/PostCreate';
import PostEdit from './pages/PostEdit';
import PostCategory from './pages/PostCategory'; import PostCategory from './pages/PostCategory';
import Login from './pages/Login'; import Login from './pages/Login';
import { AuthProvider } from './context/AuthContext'; import { AuthProvider } from './context/AuthContext';
@ -21,7 +23,9 @@ function App() {
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/about" element={<About />} /> <Route path="/about" element={<About />} />
<Route path="/posts" element={<PostList />} /> <Route path="/posts" element={<PostList />} />
<Route path="/posts/:id" element={<PostDetail />} />
<Route path="/postCreate" element={<PostCreate />} /> <Route path="/postCreate" element={<PostCreate />} />
<Route path="/posts/:id/edit" element={<PostEdit />} />
<Route path="/post/:category" element={<PostCategory />} /> <Route path="/post/:category" element={<PostCategory />} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
</Routes> </Routes>

66
src/pages/PostDetail.js Normal file
View 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
View 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;

View File

@ -31,7 +31,11 @@ const PostList = () => {
<ul className="space-y-4"> <ul className="space-y-4">
{posts.map(post => ( {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> <h2 className="text-xl font-semibold">{post.title}</h2>
<p className="text-gray-600">작성자: {post.author_name}</p> <p className="text-gray-600">작성자: {post.author_name}</p>
<p className="text-gray-400 text-sm"> <p className="text-gray-400 text-sm">

View File

@ -1 +1 @@
0.0.4 0.0.5