update
Some checks failed
Build And Test / build-and-push (push) Failing after 1m59s

This commit is contained in:
2025-04-22 19:24:49 +09:00
parent 58850f5ff6
commit 8ea01d2d4b
9 changed files with 143 additions and 50 deletions

View File

@ -4,7 +4,8 @@ import Navbar from './components/Navbar';
import Footer from './components/Footer'; 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 Board from './pages/Board'; import PostList from './pages/PostList';
import PostCreate from './pages/PostCreate';
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';
@ -19,7 +20,8 @@ function App() {
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/about" element={<About />} /> <Route path="/about" element={<About />} />
<Route path="/board" element={<Board />} /> <Route path="/posts" element={<PostList />} />
<Route path="/postCreate" element={<PostCreate />} />
<Route path="/post/:category" element={<PostCategory />} /> <Route path="/post/:category" element={<PostCategory />} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
</Routes> </Routes>

View File

@ -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;

20
src/api/authApi.js Normal file
View File

@ -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;

20
src/api/blogApi.js Normal file
View File

@ -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;

View File

@ -4,10 +4,10 @@ import { useAuth } from '../context/AuthContext'; // ✅ Context에서 로그
const Navbar = () => { const Navbar = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { isLoggedIn, logout } = useAuth(); // ✅ 로그인 상태 + 로그아웃 함수 const { isLoggedIn, logout } = useAuth();
const menuItems = ['home', 'about', 'board', 'etc']; // ✅ 'board' → 'posts'로 변경
const postCategories = ['developer', 'systemengineer', 'etc']; const menuItems = ['home', 'about', 'posts'];
return ( return (
<nav className="fixed w-full z-50 bg-white/90 backdrop-blur-md shadow-sm"> <nav className="fixed w-full z-50 bg-white/90 backdrop-blur-md shadow-sm">
@ -26,25 +26,6 @@ const Navbar = () => {
</Link> </Link>
))} ))}
{/* ▼ Post Dropdown */}
<div className="relative group">
<button className="text-gray-600 hover:text-[#D4AF37] flex items-center">
Post <i className="fas fa-chevron-down ml-1 text-xs"></i>
</button>
<div className="absolute hidden group-hover:block bg-white shadow-md mt-2 rounded-md py-2">
{postCategories.map(category => (
<button
key={category}
onClick={() => 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)}
</button>
))}
</div>
</div>
{/* ✅ 로그인 상태에 따른 버튼 */}
{isLoggedIn ? ( {isLoggedIn ? (
<button <button
onClick={() => { onClick={() => {

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import api from '../api/api'; import authApi from '../api/authApi';
import { useAuth } from '../context/AuthContext'; // ✅ 추가: AuthContext 사용 import { useAuth } from '../context/AuthContext'; // ✅ 추가: AuthContext 사용
const Login = () => { const Login = () => {
@ -12,10 +12,7 @@ const Login = () => {
const handleLogin = async () => { const handleLogin = async () => {
try { try {
const res = await api.post('/api/auth/login/', { const res = await authApi.post('/api/auth/login/', { email, password });
email,
password
});
// ✅ localStorage 저장 + 전역 상태 갱신 // ✅ localStorage 저장 + 전역 상태 갱신
login(res.data); // ← access, refresh 포함된 객체 login(res.data); // ← access, refresh 포함된 객체

46
src/pages/PostCreate.js Normal file
View File

@ -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 (
<div className="max-w-2xl mx-auto p-8">
<h1 className="text-2xl font-bold mb-4">게시글 작성</h1>
<input
type="text"
placeholder="제목"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="w-full px-4 py-2 mb-4 border rounded"
/>
<textarea
placeholder="내용"
value={content}
onChange={(e) => setContent(e.target.value)}
className="w-full px-4 py-2 mb-4 border rounded min-h-[150px]"
/>
<button
onClick={handleSubmit}
className="bg-[#3B82F6] text-white px-6 py-2 rounded hover:bg-blue-700"
>
작성 완료
</button>
</div>
);
};
export default PostCreate;

47
src/pages/PostList.js Normal file
View File

@ -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 (
<div className="max-w-4xl mx-auto p-8">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">게시글 목록</h1>
{isLoggedIn && (
<button
onClick={() => navigate('/postCreate')}
className="bg-[#3B82F6] text-white px-4 py-2 rounded hover:bg-blue-700"
>
게시물 등록
</button>
)}
</div>
<ul className="space-y-4">
{posts.map(post => (
<li key={post.id} className="p-4 border rounded shadow-sm bg-white">
<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">
작성일: {new Date(post.created_at).toLocaleString()}
</p>
</li>
))}
</ul>
</div>
);
};
export default PostList;

View File

@ -1 +1 @@
0.0.1 0.0.2