diff --git a/src/App.js b/src/App.js index f75ba64..f0a576f 100644 --- a/src/App.js +++ b/src/App.js @@ -4,7 +4,8 @@ import Navbar from './components/Navbar'; import Footer from './components/Footer'; import Home from './pages/Home'; import About from './pages/About'; -import Board from './pages/Board'; +import PostList from './pages/PostList'; +import PostCreate from './pages/PostCreate'; import PostCategory from './pages/PostCategory'; import Login from './pages/Login'; import { AuthProvider } from './context/AuthContext'; @@ -19,7 +20,8 @@ function App() { } /> } /> - } /> + } /> + } /> } /> } /> diff --git a/src/api/api.js b/src/api/api.js deleted file mode 100644 index 0566d98..0000000 --- a/src/api/api.js +++ /dev/null @@ -1,20 +0,0 @@ -// src/api/api.js -import axios from 'axios'; - -const api = axios.create({ - baseURL: process.env.REACT_APP_API_URL, // ✅ 환경변수에서 읽어옴 - headers: { - 'Content-Type': 'application/json', - } -}); - -// 요청에 JWT 자동 포함 -api.interceptors.request.use(config => { - const token = localStorage.getItem('access'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; -}); - -export default api; diff --git a/src/api/authApi.js b/src/api/authApi.js new file mode 100644 index 0000000..e04c1b1 --- /dev/null +++ b/src/api/authApi.js @@ -0,0 +1,20 @@ +// src/api/authApi.js +import axios from 'axios'; + +const authApi = axios.create({ + baseURL: process.env.REACT_APP_API_AUTH, + headers: { + 'Content-Type': 'application/json', + } +}); + +// JWT 자동 포함 (로그인 이후 /me 호출 등에 사용 가능) +authApi.interceptors.request.use(config => { + const token = localStorage.getItem('access'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +export default authApi; diff --git a/src/api/blogApi.js b/src/api/blogApi.js new file mode 100644 index 0000000..c70f029 --- /dev/null +++ b/src/api/blogApi.js @@ -0,0 +1,20 @@ +// src/api/blogApi.js +import axios from 'axios'; + +const blogApi = axios.create({ + baseURL: process.env.REACT_APP_API_BLOG, + headers: { + 'Content-Type': 'application/json', + } +}); + +// 게시글 작성 시에도 JWT 필요하므로 interceptor 동일하게 구성 +blogApi.interceptors.request.use(config => { + const token = localStorage.getItem('access'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +export default blogApi; diff --git a/src/components/Navbar.js b/src/components/Navbar.js index c9e3285..526a710 100644 --- a/src/components/Navbar.js +++ b/src/components/Navbar.js @@ -4,10 +4,10 @@ import { useAuth } from '../context/AuthContext'; // ✅ Context에서 로그 const Navbar = () => { const navigate = useNavigate(); - const { isLoggedIn, logout } = useAuth(); // ✅ 로그인 상태 + 로그아웃 함수 + const { isLoggedIn, logout } = useAuth(); - const menuItems = ['home', 'about', 'board', 'etc']; - const postCategories = ['developer', 'systemengineer', 'etc']; + // ✅ 'board' → 'posts'로 변경 + const menuItems = ['home', 'about', 'posts']; return ( @@ -26,25 +26,6 @@ const Navbar = () => { ))} - {/* ▼ Post Dropdown */} - - - Post - - - {postCategories.map(category => ( - navigate(`/post/${category.toLowerCase()}`)} - className="block px-4 py-2 text-gray-600 hover:bg-gray-50 hover:text-[#D4AF37] w-full text-left" - > - {category.charAt(0).toUpperCase() + category.slice(1)} - - ))} - - - - {/* ✅ 로그인 상태에 따른 버튼 */} {isLoggedIn ? ( { diff --git a/src/pages/Login.js b/src/pages/Login.js index 1f938d4..a747234 100644 --- a/src/pages/Login.js +++ b/src/pages/Login.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import api from '../api/api'; +import authApi from '../api/authApi'; import { useAuth } from '../context/AuthContext'; // ✅ 추가: AuthContext 사용 const Login = () => { @@ -12,10 +12,7 @@ const Login = () => { const handleLogin = async () => { try { - const res = await api.post('/api/auth/login/', { - email, - password - }); + const res = await authApi.post('/api/auth/login/', { email, password }); // ✅ localStorage 저장 + 전역 상태 갱신 login(res.data); // ← access, refresh 포함된 객체 diff --git a/src/pages/PostCreate.js b/src/pages/PostCreate.js new file mode 100644 index 0000000..752b3a7 --- /dev/null +++ b/src/pages/PostCreate.js @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import blogApi from '../api/blogApi'; +import { useNavigate } from 'react-router-dom'; + +const PostCreate = () => { + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const navigate = useNavigate(); + + const handleSubmit = async () => { + try { + await blogApi.post('/api/blog/create/', { title, content }); + alert('게시글이 등록되었습니다.'); + navigate('/posts'); + } catch (err) { + alert('등록 실패: ' + (err.response?.data?.detail || err.message)); + } + }; + + return ( + + 게시글 작성 + setTitle(e.target.value)} + className="w-full px-4 py-2 mb-4 border rounded" + /> + setContent(e.target.value)} + className="w-full px-4 py-2 mb-4 border rounded min-h-[150px]" + /> + + 작성 완료 + + + ); +}; + +export default PostCreate; diff --git a/src/pages/PostList.js b/src/pages/PostList.js new file mode 100644 index 0000000..88b37c8 --- /dev/null +++ b/src/pages/PostList.js @@ -0,0 +1,47 @@ +import React, { useEffect, useState } from 'react'; +import blogApi from '../api/blogApi'; +import { useNavigate } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; + +const PostList = () => { + const [posts, setPosts] = useState([]); + const navigate = useNavigate(); + const { isLoggedIn } = useAuth(); + + useEffect(() => { + blogApi.get('/api/blog/posts/') + .then(res => setPosts(res.data)) + .catch(err => console.error('게시글 목록 조회 실패:', err)); + }, []); + + return ( + + + 게시글 목록 + + {isLoggedIn && ( + navigate('/postCreate')} + className="bg-[#3B82F6] text-white px-4 py-2 rounded hover:bg-blue-700" + > + 게시물 등록 + + )} + + + + {posts.map(post => ( + + {post.title} + 작성자: {post.author_name} + + 작성일: {new Date(post.created_at).toLocaleString()} + + + ))} + + + ); +}; + +export default PostList; diff --git a/version b/version index 8a9ecc2..7bcd0e3 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.0.1 \ No newline at end of file +0.0.2 \ No newline at end of file
작성자: {post.author_name}
+ 작성일: {new Date(post.created_at).toLocaleString()} +