diff --git a/src/api/boardApi.js b/src/api/boardApi.js index ea4ad90..6839e17 100644 --- a/src/api/boardApi.js +++ b/src/api/boardApi.js @@ -1,38 +1,51 @@ -// src/api/boardApi.js -import axios from 'axios'; -import { attachAuthInterceptors } from './attachInterceptors'; +import axios from "axios"; +import { attachAuthInterceptors } from "./attachInterceptors"; const boardApi = axios.create({ baseURL: process.env.REACT_APP_API_BOARD, // ex) http://localhost:8801/api headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, }); -// JWT 인증 토큰 인터셉터 부착 +// ✅ JWT 토큰 자동 포함 attachAuthInterceptors(boardApi); -// ✅ 게시판 목록 조회 -export const fetchBoards = () => boardApi.get('/api/boards/'); +// +// ✅ 게시판 관련 +// -// ✅ 특정 게시판의 게시글 목록 조회 +// 게시판 목록 조회 +export const fetchBoards = () => boardApi.get("/api/boards/"); + +// +// ✅ 게시글 관련 (게시판 내 nested 구조) +// + +// 특정 게시판의 게시글 목록 조회 export const fetchPosts = (boardSlug, params = {}) => boardApi.get(`/api/boards/${boardSlug}/posts/`, { params }); -// ✅ 특정 게시글 상세 조회 export const fetchPostDetail = (boardSlug, postId) => boardApi.get(`/api/boards/${boardSlug}/posts/${postId}/`); -// ✅ 게시글 생성 +// 게시글 생성 export const createPost = (boardSlug, data) => boardApi.post(`/api/boards/${boardSlug}/posts/`, data); -// ✅ 게시글 수정 +// 게시글 수정 export const updatePost = (boardSlug, postId, data) => boardApi.put(`/api/boards/${boardSlug}/posts/${postId}/`, data); -// ✅ 게시글 삭제 +// 게시글 삭제 export const deletePost = (boardSlug, postId) => boardApi.delete(`/api/boards/${boardSlug}/posts/${postId}/`); +// +// ✅ 게시글 단일 조회 (boardSlug 없이 ID만으로 조회) +// + +// 단일 게시글 상세 조회 (id 기반) +export const getPost = (postId) => boardApi.get(`/api/posts/${postId}/`); + export default boardApi; diff --git a/src/components/Board/PostDetail.js b/src/components/Board/PostDetail.js new file mode 100644 index 0000000..8eb46e5 --- /dev/null +++ b/src/components/Board/PostDetail.js @@ -0,0 +1,71 @@ +// src/components/Board/PostDetail.js +import React, { useEffect, useState } from "react"; +import { fetchPostDetail, deletePost } from "../../api/boardApi"; +import { useAuth } from "../../context/AuthContext"; + +const PostDetail = ({ postId, boardSlug, onClose, onDeleted }) => { + const [post, setPost] = useState(null); + const { user } = useAuth(); + + useEffect(() => { + const fetch = async () => { + try { + const res = await fetchPostDetail(boardSlug, postId); // ✅ boardSlug 포함 요청 + setPost(res.data); + } catch (err) { + console.error("❌ 게시물 조회 실패", err); + alert("게시글을 불러오지 못했습니다."); + onClose(); + } + }; + fetch(); + }, [postId, boardSlug, onClose]); // ✅ boardSlug 의존성 포함 + + const handleDelete = async () => { + if (!window.confirm("정말 삭제하시겠습니까?")) return; + try { + await deletePost(boardSlug, postId); // ✅ boardSlug 포함 + alert("✅ 삭제되었습니다."); + onDeleted(); + onClose(); + } catch (err) { + console.error("❌ 삭제 실패", err); + alert("삭제에 실패했습니다."); + } + }; + + if (!post) return null; + + const isAuthor = user?.email === post.author_name; + + return ( +
+

{post.title}

+

작성자: {post.author_name}

+

+ 작성일: {new Date(post.created_at).toLocaleString()} +

+ +
{post.content}
+ +
+ + {isAuthor && ( + + )} +
+
+ ); +}; + +export default PostDetail; diff --git a/src/components/Board/PostForm.js b/src/components/Board/PostForm.js index 52eab7e..866bff5 100644 --- a/src/components/Board/PostForm.js +++ b/src/components/Board/PostForm.js @@ -1,4 +1,3 @@ -// src/components/Board/PostForm.js import React, { useState } from "react"; import { createPost } from "../../api/boardApi"; @@ -34,11 +33,11 @@ const PostForm = ({ boardSlug, onClose, onCreated }) => { console.log("✅ 전송할 payload:", payload); try { - await createPost(boardSlug, payload); + await createPost(boardSlug, payload); // ✅ 백엔드 호환: slug 사용 alert("✅ 게시글이 등록되었습니다."); - setForm({ title: "", content: "", tags: "" }); - onCreated(); - onClose(); + setForm({ title: "", content: "", tags: "" }); // 폼 초기화 + onCreated(); // 목록 새로고침 트리거 + onClose(); // 폼 닫기 } catch (err) { console.error("❌ 게시글 등록 실패", err); console.log("🚨 서버 응답 내용:", err.response?.data); diff --git a/src/components/Board/PostList.js b/src/components/Board/PostList.js index e6a9f26..c22fa10 100644 --- a/src/components/Board/PostList.js +++ b/src/components/Board/PostList.js @@ -1,43 +1,113 @@ -// src/components/Board/PostList.js -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback } from "react"; import { fetchPosts } from "../../api/boardApi"; +import PostDetail from "./PostDetail"; const PostList = ({ boardSlug, search, tag }) => { const [posts, setPosts] = useState([]); + const [selectedPostId, setSelectedPostId] = useState(null); + const [drawerOpen, setDrawerOpen] = useState(false); + + const loadPosts = useCallback(async () => { + try { + const params = {}; + if (tag) params.tag = tag; + if (search) params.search = search; + + const res = await fetchPosts(boardSlug, params); + setPosts(res.data); + } catch (err) { + console.error("❌ 게시글 목록 불러오기 실패", err); + } + }, [boardSlug, tag, search]); useEffect(() => { - if (!boardSlug) return; + if (boardSlug) { + loadPosts(); + } + }, [boardSlug, loadPosts]); - const loadPosts = async () => { - try { - const params = {}; - if (tag) params.tag = tag; - if (search) params.search = search; - - const res = await fetchPosts(boardSlug, params); - setPosts(res.data); - } catch (err) { - console.error("❌ 게시글 목록 불러오기 실패", err); + // ✅ ESC 키로 닫기 + useEffect(() => { + const handleKeyDown = (e) => { + if (e.key === "Escape") { + setDrawerOpen(false); + setSelectedPostId(null); } }; - loadPosts(); - }, [boardSlug, search, tag]); + if (drawerOpen) { + window.addEventListener("keydown", handleKeyDown); + } else { + window.removeEventListener("keydown", handleKeyDown); + } + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [drawerOpen]); + + const handleSelectPost = (postId) => { + setSelectedPostId(postId); + setDrawerOpen(true); + }; + + const handleCloseDrawer = () => { + setDrawerOpen(false); + setSelectedPostId(null); + }; if (!boardSlug) return

📂 게시판을 선택해주세요.

; return ( -
+

📝 게시글 목록

+ + {/* ✅ 슬라이딩 Drawer */} +
+
+ +
+
+ {selectedPostId && ( + + )} +
+
+ + {/* ✅ 배경 오버레이 */} + {drawerOpen && ( +
+ )}
); }; diff --git a/version b/version index bc719c4..0e365fb 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.0.17-rc1 \ No newline at end of file +0.0.17-rc2 \ No newline at end of file