This commit is contained in:
		@ -1,4 +1,5 @@
 | 
				
			|||||||
REACT_APP_API_AUTH=http://127.0.0.1:8000
 | 
					REACT_APP_API_AUTH=http://127.0.0.1:8000
 | 
				
			||||||
REACT_APP_API_BLOG=http://127.0.0.1:8800
 | 
					REACT_APP_API_BLOG=http://127.0.0.1:8800
 | 
				
			||||||
 | 
					REACT_APP_API_BOARD=http://127.0.0.1:8801
 | 
				
			||||||
REACT_APP_API_TODO=http://127.0.0.1:8880
 | 
					REACT_APP_API_TODO=http://127.0.0.1:8880
 | 
				
			||||||
REACT_APP_API_ANSIBLE=http://127.0.0.1:8888
 | 
					REACT_APP_API_ANSIBLE=http://127.0.0.1:8888
 | 
				
			||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
REACT_APP_API_AUTH=https://www.icurfer.com
 | 
					REACT_APP_API_AUTH=https://www.icurfer.com
 | 
				
			||||||
REACT_APP_API_BLOG=https://www.icurfer.com
 | 
					REACT_APP_API_BLOG=https://www.icurfer.com
 | 
				
			||||||
 | 
					REACT_APP_API_BOARD=https://www.icurfer.com
 | 
				
			||||||
REACT_APP_API_TODO=https://www.icurfer.com
 | 
					REACT_APP_API_TODO=https://www.icurfer.com
 | 
				
			||||||
REACT_APP_API_ANSIBLE=https://www.icurfer.com
 | 
					REACT_APP_API_ANSIBLE=https://www.icurfer.com
 | 
				
			||||||
@ -13,6 +13,7 @@ import Login from "./pages/Login";
 | 
				
			|||||||
import Register from './pages/Register';
 | 
					import Register from './pages/Register';
 | 
				
			||||||
import Profile from "./pages/Profile";
 | 
					import Profile from "./pages/Profile";
 | 
				
			||||||
import TaskPage from "./pages/TaskPage"; 
 | 
					import TaskPage from "./pages/TaskPage"; 
 | 
				
			||||||
 | 
					import BoardsPage from "./pages/BoardsPage";
 | 
				
			||||||
import AnsiblePage from "./pages/Ansible";
 | 
					import AnsiblePage from "./pages/Ansible";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { AuthProvider } from "./context/AuthContext";
 | 
					import { AuthProvider } from "./context/AuthContext";
 | 
				
			||||||
@ -36,6 +37,7 @@ function App() {
 | 
				
			|||||||
              <Route path="/register" element={<Register />} />
 | 
					              <Route path="/register" element={<Register />} />
 | 
				
			||||||
              <Route path="/profile" element={<Profile />} />
 | 
					              <Route path="/profile" element={<Profile />} />
 | 
				
			||||||
              <Route path="/tasks" element={<TaskPage />} />
 | 
					              <Route path="/tasks" element={<TaskPage />} />
 | 
				
			||||||
 | 
					              <Route path="/boards" element={<BoardsPage />} />
 | 
				
			||||||
              <Route path="/ansible" element={<AnsiblePage />} />
 | 
					              <Route path="/ansible" element={<AnsiblePage />} />
 | 
				
			||||||
            </Routes>
 | 
					            </Routes>
 | 
				
			||||||
          </main>
 | 
					          </main>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										38
									
								
								src/api/boardApi.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/api/boardApi.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					// src/api/boardApi.js
 | 
				
			||||||
 | 
					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',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// JWT 인증 토큰 인터셉터 부착
 | 
				
			||||||
 | 
					attachAuthInterceptors(boardApi);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ✅ 게시판 목록 조회
 | 
				
			||||||
 | 
					export const fetchBoards = () => boardApi.get('/api/boards/');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ✅ 특정 게시판의 게시글 목록 조회
 | 
				
			||||||
 | 
					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}/`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default boardApi;
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/components/Board/BoardList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/components/Board/BoardList.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					// src/components/Board/BoardList.js
 | 
				
			||||||
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import boardApi from "../../api/boardApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BoardList = ({ onSelectBoard, selectedBoard }) => {
 | 
				
			||||||
 | 
					  const [boards, setBoards] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const fetchBoards = async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const res = await boardApi.get("/api/boards/");
 | 
				
			||||||
 | 
					        setBoards(res.data);
 | 
				
			||||||
 | 
					      } catch (err) {
 | 
				
			||||||
 | 
					        console.error("❌ 게시판 목록 불러오기 실패", err);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    fetchBoards();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <h2 className="text-lg font-bold mb-2">📁 게시판</h2>
 | 
				
			||||||
 | 
					      <ul className="space-y-1">
 | 
				
			||||||
 | 
					        {boards.map((board) => (
 | 
				
			||||||
 | 
					          <li
 | 
				
			||||||
 | 
					            key={board.slug}
 | 
				
			||||||
 | 
					            onClick={() => onSelectBoard(board.slug)}
 | 
				
			||||||
 | 
					            className={`cursor-pointer px-2 py-1 rounded hover:bg-blue-100 ${
 | 
				
			||||||
 | 
					              selectedBoard === board.slug ? "bg-blue-200 font-semibold" : ""
 | 
				
			||||||
 | 
					            }`}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            {board.name}
 | 
				
			||||||
 | 
					          </li>
 | 
				
			||||||
 | 
					        ))}
 | 
				
			||||||
 | 
					      </ul>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default BoardList;
 | 
				
			||||||
							
								
								
									
										45
									
								
								src/components/Board/PostList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/components/Board/PostList.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					// src/components/Board/PostList.js
 | 
				
			||||||
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { fetchPosts } from "../../api/boardApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PostList = ({ boardSlug, search, tag }) => {
 | 
				
			||||||
 | 
					  const [posts, setPosts] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (!boardSlug) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    loadPosts();
 | 
				
			||||||
 | 
					  }, [boardSlug, search, tag]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!boardSlug) return <p>📂 게시판을 선택해주세요.</p>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <h2 className="text-lg font-bold mb-2">📝 게시글 목록</h2>
 | 
				
			||||||
 | 
					      <ul className="space-y-2">
 | 
				
			||||||
 | 
					        {posts.map((post) => (
 | 
				
			||||||
 | 
					          <li key={post.id} className="border p-2 rounded shadow">
 | 
				
			||||||
 | 
					            <h3 className="font-semibold">{post.title}</h3>
 | 
				
			||||||
 | 
					            <p className="text-sm text-gray-500">작성자: {post.author_name}</p>
 | 
				
			||||||
 | 
					            <p className="text-gray-700 line-clamp-2">{post.content}</p>
 | 
				
			||||||
 | 
					          </li>
 | 
				
			||||||
 | 
					        ))}
 | 
				
			||||||
 | 
					      </ul>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default PostList;
 | 
				
			||||||
							
								
								
									
										72
									
								
								src/components/Board/PostSearchPanel.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/components/Board/PostSearchPanel.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					// src/components/Board/PostSearchPanel.js
 | 
				
			||||||
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import boardApi from "../../api/boardApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PostSearchPanel = ({ boardSlug, onSearch, onTagSelect }) => {
 | 
				
			||||||
 | 
					  const [searchInput, setSearchInput] = useState("");
 | 
				
			||||||
 | 
					  const [tags, setTags] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (!boardSlug) {
 | 
				
			||||||
 | 
					      setTags([]); // ✅ 게시판 선택 해제 시 태그도 비움
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const fetchTags = async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const res = await boardApi.get(`/api/boards/${boardSlug}/tags/`);
 | 
				
			||||||
 | 
					        setTags(res.data); // ['python', 'devops', ...]
 | 
				
			||||||
 | 
					      } catch (err) {
 | 
				
			||||||
 | 
					        console.error("❌ 태그 불러오기 실패", err);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fetchTags();
 | 
				
			||||||
 | 
					  }, [boardSlug]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleSearch = (e) => {
 | 
				
			||||||
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					    onSearch(searchInput);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // ✅ boardSlug 없으면 UI 자체를 숨김
 | 
				
			||||||
 | 
					  if (!boardSlug) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <h2 className="text-lg font-bold mb-2">🔍 검색 및 태그</h2>
 | 
				
			||||||
 | 
					      <form onSubmit={handleSearch} className="mb-4">
 | 
				
			||||||
 | 
					        <input
 | 
				
			||||||
 | 
					          type="text"
 | 
				
			||||||
 | 
					          placeholder="검색어 입력"
 | 
				
			||||||
 | 
					          value={searchInput}
 | 
				
			||||||
 | 
					          onChange={(e) => setSearchInput(e.target.value)}
 | 
				
			||||||
 | 
					          className="w-full border px-2 py-1 rounded mb-2"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          type="submit"
 | 
				
			||||||
 | 
					          className="w-full bg-blue-500 text-white py-1 rounded"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          검색
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div>
 | 
				
			||||||
 | 
					        <h3 className="font-semibold mb-1">📌 태그 목록</h3>
 | 
				
			||||||
 | 
					        <div className="flex flex-wrap gap-2">
 | 
				
			||||||
 | 
					          {tags.map((tag) => (
 | 
				
			||||||
 | 
					            <span
 | 
				
			||||||
 | 
					              key={tag}
 | 
				
			||||||
 | 
					              onClick={() => onTagSelect(tag)}
 | 
				
			||||||
 | 
					              className="cursor-pointer bg-gray-200 px-2 py-1 rounded hover:bg-blue-200"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              #{tag}
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					          ))}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default PostSearchPanel;
 | 
				
			||||||
							
								
								
									
										37
									
								
								src/components/Board/_unused_BoardNavLinks.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/components/Board/_unused_BoardNavLinks.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					// src/components/Board/BoardNavLinks.js
 | 
				
			||||||
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { Link } from "react-router-dom";
 | 
				
			||||||
 | 
					import boardApi from "../../api/boardApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BoardNavLinks = () => {
 | 
				
			||||||
 | 
					  const [boards, setBoards] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const fetchBoards = async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const res = await boardApi.get("/api/boards/");
 | 
				
			||||||
 | 
					        setBoards(res.data);
 | 
				
			||||||
 | 
					      } catch (err) {
 | 
				
			||||||
 | 
					        console.error("❌ 게시판 목록 불러오기 실패", err);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fetchBoards();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      {boards.map((board) => (
 | 
				
			||||||
 | 
					        <Link
 | 
				
			||||||
 | 
					          key={board.slug}
 | 
				
			||||||
 | 
					          to={`/api/boards/${board.slug}`}
 | 
				
			||||||
 | 
					          className="text-gray-600 hover:text-[#D4AF37]"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {board.name}
 | 
				
			||||||
 | 
					        </Link>
 | 
				
			||||||
 | 
					      ))}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default BoardNavLinks;
 | 
				
			||||||
@ -6,16 +6,19 @@ const Navbar = () => {
 | 
				
			|||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
  const { isLoggedIn, logout, user } = useAuth();
 | 
					  const { isLoggedIn, logout, user } = useAuth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // ✅ 로그인 상태에 따라 메뉴 구성
 | 
					  // 로그인 상태에 따라 메뉴 구성
 | 
				
			||||||
  const menuItems = [
 | 
					  const menuItems = [
 | 
				
			||||||
    { name: "home", path: "/" },
 | 
					    { name: "home", path: "/" },
 | 
				
			||||||
    { name: "about", path: "/about" },
 | 
					    { name: "about", path: "/about" },
 | 
				
			||||||
    { name: "posts", path: "/posts" },
 | 
					    { name: "posts", path: "/posts" },
 | 
				
			||||||
 | 
					    { name: "boards", path: "/boards" },
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // 로그인 상태일 때 추가되는 메뉴
 | 
				
			||||||
  if (isLoggedIn) {
 | 
					  if (isLoggedIn) {
 | 
				
			||||||
    menuItems.push({ name: "tasks", path: "/tasks" });
 | 
					    menuItems.push({ name: "tasks", path: "/tasks" });
 | 
				
			||||||
    menuItems.push({ name: "ansible", path: "/ansible" });
 | 
					    menuItems.push({ name: "ansible", path: "/ansible" });
 | 
				
			||||||
 | 
					    // menuItems.push({ name: "boards", path: "/boards" });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										81
									
								
								src/components/_unused_Navbar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/components/_unused_Navbar.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { Link, useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					import { useAuth } from "../context/AuthContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Navbar = () => {
 | 
				
			||||||
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					  const { isLoggedIn, logout, user } = useAuth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // ✅ 로그인 상태에 따라 메뉴 구성
 | 
				
			||||||
 | 
					  const menuItems = [
 | 
				
			||||||
 | 
					    { name: "home", path: "/" },
 | 
				
			||||||
 | 
					    { name: "about", path: "/about" },
 | 
				
			||||||
 | 
					    { name: "posts", path: "/posts" },
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isLoggedIn) {
 | 
				
			||||||
 | 
					    menuItems.push({ name: "tasks", path: "/tasks" });
 | 
				
			||||||
 | 
					    menuItems.push({ name: "ansible", path: "/ansible" });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <nav className="fixed w-full z-50 bg-white/90 backdrop-blur-md shadow-sm">
 | 
				
			||||||
 | 
					      <div className="container mx-auto px-4">
 | 
				
			||||||
 | 
					        <div className="flex justify-between items-center h-16">
 | 
				
			||||||
 | 
					          <Link to="/" className="text-2xl font-bold text-[#3B82F6]">
 | 
				
			||||||
 | 
					            Dev
 | 
				
			||||||
 | 
					          </Link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className="hidden md:flex items-center space-x-8">
 | 
				
			||||||
 | 
					            {menuItems.map((item) => (
 | 
				
			||||||
 | 
					              <Link
 | 
				
			||||||
 | 
					                key={item.name}
 | 
				
			||||||
 | 
					                to={item.path}
 | 
				
			||||||
 | 
					                className="text-gray-600 hover:text-[#D4AF37]"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                {item.name.charAt(0).toUpperCase() + item.name.slice(1)}
 | 
				
			||||||
 | 
					              </Link>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {isLoggedIn ? (
 | 
				
			||||||
 | 
					              <>
 | 
				
			||||||
 | 
					                <Link
 | 
				
			||||||
 | 
					                  to="/profile"
 | 
				
			||||||
 | 
					                  className="bg-[#8b0000] text-white px-4 py-2 rounded hover:bg-gray-300"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  내 정보 {user?.grade && `(${user.grade})`}
 | 
				
			||||||
 | 
					                </Link>
 | 
				
			||||||
 | 
					                <button
 | 
				
			||||||
 | 
					                  onClick={() => {
 | 
				
			||||||
 | 
					                    logout();
 | 
				
			||||||
 | 
					                    navigate("/login");
 | 
				
			||||||
 | 
					                  }}
 | 
				
			||||||
 | 
					                  className="bg-gray-200 text-gray-700 px-4 py-2 rounded hover:bg-gray-300"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  로그아웃
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					              </>
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              <>
 | 
				
			||||||
 | 
					                <Link
 | 
				
			||||||
 | 
					                  to="/login"
 | 
				
			||||||
 | 
					                  className="bg-[#3B82F6] text-white px-4 py-2 rounded hover:bg-blue-700"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  로그인
 | 
				
			||||||
 | 
					                </Link>
 | 
				
			||||||
 | 
					                <Link
 | 
				
			||||||
 | 
					                  to="/register"
 | 
				
			||||||
 | 
					                  className="bg-gray-200 text-gray-700 px-4 py-2 rounded hover:bg-gray-300"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  회원가입
 | 
				
			||||||
 | 
					                </Link>
 | 
				
			||||||
 | 
					              </>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Navbar;
 | 
				
			||||||
							
								
								
									
										43
									
								
								src/pages/BoardsPage.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/pages/BoardsPage.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					// src/pages/BoardsPage.js
 | 
				
			||||||
 | 
					import React, { useState } from "react";
 | 
				
			||||||
 | 
					import BoardList from "../components/Board/BoardList";
 | 
				
			||||||
 | 
					import PostList from "../components/Board/PostList";
 | 
				
			||||||
 | 
					import PostSearchPanel from "../components/Board/PostSearchPanel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BoardsPage = () => {
 | 
				
			||||||
 | 
					  const [selectedBoard, setSelectedBoard] = useState(null);
 | 
				
			||||||
 | 
					  const [searchQuery, setSearchQuery] = useState("");
 | 
				
			||||||
 | 
					  const [selectedTag, setSelectedTag] = useState("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="grid grid-cols-12 gap-4 p-4">
 | 
				
			||||||
 | 
					      {/* 좌측: 게시판 리스트 */}
 | 
				
			||||||
 | 
					      <div className="col-span-2 bg-white p-4 shadow rounded">
 | 
				
			||||||
 | 
					        <BoardList
 | 
				
			||||||
 | 
					          onSelectBoard={setSelectedBoard}
 | 
				
			||||||
 | 
					          selectedBoard={selectedBoard}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* 중앙: 게시글 리스트 */}
 | 
				
			||||||
 | 
					      <div className="col-span-7 bg-white p-4 shadow rounded">
 | 
				
			||||||
 | 
					        <PostList
 | 
				
			||||||
 | 
					          boardSlug={selectedBoard}
 | 
				
			||||||
 | 
					          search={searchQuery}
 | 
				
			||||||
 | 
					          tag={selectedTag}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* 우측: 검색 + 태그 */}
 | 
				
			||||||
 | 
					      <div className="col-span-3 bg-white p-4 shadow rounded">
 | 
				
			||||||
 | 
					        <PostSearchPanel
 | 
				
			||||||
 | 
					          boardSlug={selectedBoard}
 | 
				
			||||||
 | 
					          onSearch={setSearchQuery}
 | 
				
			||||||
 | 
					          onTagSelect={setSelectedTag}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default BoardsPage;
 | 
				
			||||||
		Reference in New Issue
	
	Block a user