nav구조 변경
All checks were successful
Build And Test / build-and-push (push) Successful in 1m8s

This commit is contained in:
2025-05-24 00:58:04 +09:00
parent 8997539c9a
commit b94f6cea2e
12 changed files with 263 additions and 137 deletions

BIN
public/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

View File

@ -1,4 +1,5 @@
import React from "react";
// src/App.js
import React, { useEffect } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
@ -12,38 +13,53 @@ import PostCategory from "./pages/PostCategory";
import Login from "./pages/Login";
import Register from './pages/Register';
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 Layout from "./components/Layout/Layout";
import { AuthProvider } from "./context/AuthContext";
import { AuthProvider, useAuth } from "./context/AuthContext";
import authApi from "./api/authApi";
import { attachAuthInterceptors } from "./api/attachInterceptors";
function AppContent() {
const { logout } = useAuth();
useEffect(() => {
attachAuthInterceptors(authApi, logout); // ✅ axios 인터셉터에 logout 연결
}, [logout]);
return (
<Router>
<div className="flex flex-col min-h-screen">
<Navbar />
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/posts" element={<PostList />} />
<Route path="/posts/:id" element={<PostDetail />} />
<Route path="/postCreate" element={<PostCreate />} />
<Route path="/posts/:id/edit" element={<PostEdit />} />
<Route path="/post/:category" element={<PostCategory />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/profile" element={<Profile />} />
<Route path="/tasks" element={<TaskPage />} />
<Route path="/boards" element={<BoardsPage />} />
<Route path="/ansible" element={<AnsiblePage />} />
</Routes>
</Layout>
<Footer />
</div>
</Router>
);
}
function App() {
return (
<AuthProvider>
<Router>
<div className="flex flex-col min-h-screen">
<Navbar />
<main className="flex-grow pt-16">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/posts" element={<PostList />} />
<Route path="/posts/:id" element={<PostDetail />} />
<Route path="/postCreate" element={<PostCreate />} />
<Route path="/posts/:id/edit" element={<PostEdit />} />
<Route path="/post/:category" element={<PostCategory />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/profile" element={<Profile />} />
<Route path="/tasks" element={<TaskPage />} />
<Route path="/boards" element={<BoardsPage />} />
<Route path="/ansible" element={<AnsiblePage />} />
</Routes>
</main>
<Footer />
</div>
</Router>
<AppContent /> {/* ✅ useAuth는 이 내부에서만 사용 가능 */}
</AuthProvider>
);
}

View File

@ -1,7 +1,7 @@
// src/api/attachInterceptors.js
import { refreshAccessToken } from './tokenUtils';
export const attachAuthInterceptors = (axiosInstance) => {
// 요청 시 access token 자동 첨부
export const attachAuthInterceptors = (axiosInstance, logout) => {
axiosInstance.interceptors.request.use(config => {
const token = localStorage.getItem('access');
if (token) {
@ -10,7 +10,6 @@ export const attachAuthInterceptors = (axiosInstance) => {
return config;
});
// 응답 시 access 만료 → refresh 재시도
axiosInstance.interceptors.response.use(
res => res,
async err => {
@ -29,9 +28,8 @@ export const attachAuthInterceptors = (axiosInstance) => {
return axiosInstance(originalRequest);
} catch (refreshError) {
console.error('토큰 갱신 실패', refreshError);
localStorage.removeItem('access');
localStorage.removeItem('refresh');
window.location.href = '/login'; // 또는 useNavigate 사용 가능
if (logout) logout(); // ✅ Context logout 반영
window.location.href = '/login';
}
}

View File

@ -0,0 +1,16 @@
// src/components/Layout/Layout.js
import React from "react";
import { useLocation } from "react-router-dom";
const Layout = ({ children }) => {
const location = useLocation();
const isHome = location.pathname === "/";
return (
<main className={`${isHome ? "pt-0" : "pt-16"} flex-grow`}>
{children}
</main>
);
};
export default Layout;

View File

@ -1,80 +1,77 @@
import React from "react";
import { Link, useNavigate } from "react-router-dom";
import { Link, useNavigate, useLocation } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
const Navbar = () => {
const navigate = useNavigate();
const location = useLocation();
const { isLoggedIn, logout, user } = useAuth();
// 로그인 상태에 따라 메뉴 구성
const isHome = location.pathname === "/";
const menuItems = [
{ name: "home", path: "/" },
{ name: "about", path: "/about" },
{ name: "posts", path: "/posts" },
{ name: "boards", path: "/boards" },
...(isLoggedIn ? [
{ name: "tasks", path: "/tasks" },
{ name: "ansible", path: "/ansible" },
] : []),
];
// 로그인 상태일 때 추가되는 메뉴
if (isLoggedIn) {
menuItems.push({ name: "tasks", path: "/tasks" });
menuItems.push({ name: "ansible", path: "/ansible" });
// menuItems.push({ name: "boards", path: "/boards" });
}
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) => (
<nav className={`fixed top-0 w-full z-50 h-16 ${isHome ? "text-white bg-transparent" : "text-black bg-white shadow"}`}>
<div className="container mx-auto px-4 py-3 flex justify-between items-center">
<Link to="/" className="text-xl font-bold tracking-wide">
ICURFER
</Link>
<div className="hidden md:flex space-x-6 font-semibold">
{menuItems.map((item) => (
<Link
key={item.name}
to={item.path}
className={`hover:text-yellow-400 transition capitalize`}
>
{item.name}
</Link>
))}
</div>
<div className="space-x-2 flex items-center ">
{isLoggedIn ? (
<>
<Link
key={item.name}
to={item.path}
className="text-gray-600 hover:text-[#D4AF37]"
to="/profile"
className={`px-4 py-2 rounded-full shadow ${isHome ? "bg-white text-black" : "bg-gray-200 text-gray-800"}`}
>
{item.name.charAt(0).toUpperCase() + item.name.slice(1)}
정보 | 등급: {user?.grade && `(${user.grade})`}
</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>
<button
onClick={() => {
logout();
navigate("/login");
}}
className={`px-4 py-2 rounded-full ${isHome ? "bg-white text-black" : "bg-gray-200 text-gray-800"} hover:bg-gray-300`}
>
로그아웃
</button>
</>
) : (
<>
<Link
to="/login"
className={`px-4 py-2 rounded-full ${isHome ? "bg-gray-200 text-gray-700 hover:bg-gray-300" : "bg-blue-600 text-white"}`}
>
로그인
</Link>
<Link
to="/register"
className={`px-4 py-2 rounded-full ${isHome ? "bg-gray-700 text-white hover:bg-gray-300" : "bg-gray-200 text-gray-800"}`}
>
회원가입
</Link>
</>
)}
</div>
</div>
</nav>

View File

@ -6,16 +6,19 @@ const Navbar = () => {
const navigate = useNavigate();
const { isLoggedIn, logout, user } = useAuth();
// 로그인 상태에 따라 메뉴 구성
// 로그인 상태에 따라 메뉴 구성
const menuItems = [
{ name: "home", path: "/" },
{ name: "about", path: "/about" },
{ name: "posts", path: "/posts" },
{ name: "boards", path: "/boards" },
];
// 로그인 상태일 때 추가되는 메뉴
if (isLoggedIn) {
menuItems.push({ name: "tasks", path: "/tasks" });
menuItems.push({ name: "ansible", path: "/ansible" });
// menuItems.push({ name: "boards", path: "/boards" });
}
return (

View File

@ -1,11 +1,15 @@
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { AuthProvider } from './context/AuthContext'; // ✅ 추가
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
<AuthProvider> {/* ✅ 전역 로그인 상태 관리 */}
<App />
</AuthProvider>
</React.StrictMode>
);
);

View File

@ -10,10 +10,10 @@ const About = () => {
animation: false,
radar: {
indicator: [
{ name: '클라우드', max: 100 },
{ name: '리눅스', max: 100 },
{ name: '데브옵스', max: 100 },
{ name: 'CI/CD', max: 100 },
{ name: '클라우드', max: 120 },
{ name: '리눅스(우분투)', max: 130 },
{ name: '쿠버네티스', max: 110 },
{ name: 'CI/CD', max: 120 },
{ name: '기타', max: 100 }
]
},
@ -39,18 +39,26 @@ const About = () => {
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 mb-20">
<div>
<h3 className="text-2xl font-bold mb-6">우리의 비전</h3>
<p className="text-gray-600 leading-relaxed">
엔지니어에게 필요한 관리 포탈을 만듭니다.
</p>
<ul className="space-y-4">
<li className="flex items-center">
<i className="fas fa-check text-[#3B82F6] mr-3"></i><span> </span>
</li>
<li className="flex items-center">
<i className="fas fa-check text-[#3B82F6] mr-3"></i><span> </span>
</li>
<li className="flex items-center">
<i className="fas fa-check text-[#3B82F6] mr-3"></i><span>-</span>
</li>
</ul>
</div>
<div>
<h3 className="text-2xl font-bold mb-6">핵심 가치</h3>
<ul className="space-y-4">
<li className="flex items-center">
<i className="fas fa-check text-[#3B82F6] mr-3"></i><span> </span>
<i className="fas fa-check text-[#3B82F6] mr-3"></i><span></span>
</li>
<li className="flex items-center">
<i className="fas fa-check text-[#3B82F6] mr-3"></i><span>-</span>
<i className="fas fa-check text-[#3B82F6] mr-3"></i><span></span>
</li>
<li className="flex items-center">
<i className="fas fa-check text-[#3B82F6] mr-3"></i><span>-</span>
@ -59,7 +67,7 @@ const About = () => {
</div>
</div>
<div className="mb-20">
<h3 className="text-2xl font-bold mb-8 text-center">전문 역량</h3>
<h3 className="text-2xl font-bold mb-8 text-center">교육 역량</h3>
<div ref={chartRef} className="w-full h-[400px]"></div>
</div>
<div className="text-center">

View File

@ -13,7 +13,7 @@ const Board = () => {
);
return (
<div className="min-h-screen bg-gray-50 py-20">
<div className="min-h-screen bg-gray-50 pt-20">
<div className="container mx-auto px-4">
<div className="max-w-6xl mx-auto">
<div className="mb-8">

View File

@ -1,60 +1,81 @@
import React, { useEffect, useState } from 'react';
import blogApi from '../api/blogApi';
import { useNavigate } from 'react-router-dom';
// src/pages/Home.js
import React, { useEffect, useState } from "react";
import blogApi from "../api/blogApi";
import { Link, useNavigate } from "react-router-dom";
const Home = () => {
const [posts, setPosts] = useState([]);
const navigate = useNavigate();
useEffect(() => {
blogApi.get('/api/blog/posts/')
.then(res => setPosts(res.data))
.catch(err => console.error('게시글 목록 조회 실패:', err));
blogApi
.get("/api/blog/posts/")
.then((res) => setPosts(res.data))
.catch((err) => console.error("게시글 목록 조회 실패:", err));
}, []);
return (
<div className="min-h-screen">
<div className="relative h-screen">
<div className="absolute inset-0">
<img
src="https://public.readdy.ai/ai/img_res/41c06ed594c02f95a02361b098edd073.jpg"
className="w-full h-full object-cover"
alt="Hero Background"
/>
</div>
<div className="absolute inset-0 bg-gradient-to-r from-black/70 to-transparent">
<div className="container mx-auto px-4 h-full flex items-center">
<div className="max-w-2xl text-white">
<h1 className="text-5xl font-bold mb-6">System Management Portal</h1>
<p className="text-xl mb-8">데모 사이트 입니다.</p>
<button className="bg-[#3B82F6] text-white px-8 py-3 rounded-lg">개발 입니다.</button>
</div>
<div className="min-h-screen bg-white">
{/* Hero Section */}
<section className="relative h-[80vh]">
<img
src="/bg.png"
alt="Hero Background"
className="absolute inset-0 w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-r from-[#000000cc] to-[#00000000]" />
<div className="relative z-10 flex items-center justify-start h-full container mx-auto px-4">
<div className="text-white max-w-xl">
<h1 className="text-5xl font-bold mb-6">
IT Education <br />
Basic & Advanced
</h1>
<p className="text-lg mb-6">
Ubuntu Linux, Ansible, Docker, Kubernetes, etc.
</p>
<br/>
<h1 className="text-5xl font-bold mb-6">
System Management<br />
</h1>
<p className="text-lg mb-6">
IT System Management Portal
</p>
<Link
to="/about"
className="inline-block bg-white text-black px-6 py-3 rounded-lg font-semibold hover:opacity-90"
>
자세히 보기 &gt;
</Link>
</div>
</div>
</div>
</section>
<div className="container mx-auto px-4 py-20">
{/* Posts Section */}
<section className="container mx-auto px-4 py-20">
<h2 className="text-3xl font-bold mb-12 text-center">최신 포스트</h2>
<ul className="grid grid-cols-1 md:grid-cols-3 gap-8">
{posts.length > 0 ? (
posts.map(post => (
posts.map((post) => (
<li
key={post.id}
className="p-4 border rounded shadow-sm bg-white hover:bg-gray-50 cursor-pointer list-none"
className="bg-white p-6 rounded-lg shadow-md hover:shadow-lg transition cursor-pointer list-none border"
onClick={() => navigate(`/posts/${post.id}`)}
>
<h2 className="text-xl font-semibold">{post.title}</h2>
<p className="text-gray-600">작성자: {post.author_name}</p>
<h3 className="text-xl font-semibold mb-2">{post.title}</h3>
<p className="text-gray-700 mb-1">작성자: {post.author_name}</p>
<p className="text-gray-400 text-sm">
작성일: {new Date(post.created_at).toLocaleString()}
{new Date(post.created_at).toLocaleString()}
</p>
</li>
))
) : (
<p className="text-center col-span-3 text-gray-500">게시글이 없습니다.</p>
<p className="text-center col-span-3 text-gray-500">
게시글이 없습니다.
</p>
)}
</ul>
</div>
</section>
</div>
);
};

63
src/pages/_unused_Home.js Normal file
View File

@ -0,0 +1,63 @@
import React, { useEffect, useState } from 'react';
import blogApi from '../api/blogApi';
import { useNavigate } from 'react-router-dom';
const Home = () => {
const [posts, setPosts] = useState([]);
const navigate = useNavigate();
useEffect(() => {
blogApi.get('/api/blog/posts/')
.then(res => setPosts(res.data))
.catch(err => console.error('게시글 목록 조회 실패:', err));
}, []);
return (
<div className="min-h-screen">
<div className="relative h-screen">
<div className="absolute inset-0">
<img
src="/bg.png"
className="w-full h-full object-cover"
// className="w-full h-full object-cover"
alt="Hero Background"
/>
</div>
<div className="absolute inset-0 bg-gradient-to-r from-black/70 to-transparent">
<div className="container mx-auto px-4 h-full flex items-center">
<div className="max-w-2xl text-white">
<h1 className="text-5xl font-bold mb-6">System Management Portal</h1>
<p className="text-xl mb-8">데모 사이트 입니다.</p>
<button className="bg-[#3B82F6] text-white px-8 py-3 rounded-lg">개발 입니다.</button>
</div>
</div>
</div>
</div>
<div className="container mx-auto px-4 py-20">
<h2 className="text-3xl font-bold mb-12 text-center">최신 포스트</h2>
<ul className="grid grid-cols-1 md:grid-cols-3 gap-8">
{posts.length > 0 ? (
posts.map(post => (
<li
key={post.id}
className="p-4 border rounded shadow-sm bg-white hover:bg-gray-50 cursor-pointer list-none"
onClick={() => navigate(`/posts/${post.id}`)}
>
<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>
))
) : (
<p className="text-center col-span-3 text-gray-500">게시글이 없습니다.</p>
)}
</ul>
</div>
</div>
);
};
export default Home;

View File

@ -1 +1 @@
0.0.15
0.0.16