This commit is contained in:
BIN
public/bg.png
Normal file
BIN
public/bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 429 KiB |
28
src/App.js
28
src/App.js
@ -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 { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
import Navbar from "./components/Navbar";
|
import Navbar from "./components/Navbar";
|
||||||
import Footer from "./components/Footer";
|
import Footer from "./components/Footer";
|
||||||
@ -15,16 +16,24 @@ import Profile from "./pages/Profile";
|
|||||||
import TaskPage from "./pages/TaskPage";
|
import TaskPage from "./pages/TaskPage";
|
||||||
import BoardsPage from "./pages/BoardsPage";
|
import BoardsPage from "./pages/BoardsPage";
|
||||||
import AnsiblePage from "./pages/Ansible";
|
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]);
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
return (
|
||||||
<AuthProvider>
|
|
||||||
<Router>
|
<Router>
|
||||||
<div className="flex flex-col min-h-screen">
|
<div className="flex flex-col min-h-screen">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="flex-grow pt-16">
|
<Layout>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/about" element={<About />} />
|
<Route path="/about" element={<About />} />
|
||||||
@ -40,10 +49,17 @@ function App() {
|
|||||||
<Route path="/boards" element={<BoardsPage />} />
|
<Route path="/boards" element={<BoardsPage />} />
|
||||||
<Route path="/ansible" element={<AnsiblePage />} />
|
<Route path="/ansible" element={<AnsiblePage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</Layout>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<AuthProvider>
|
||||||
|
<AppContent /> {/* ✅ useAuth는 이 내부에서만 사용 가능 */}
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
// src/api/attachInterceptors.js
|
||||||
import { refreshAccessToken } from './tokenUtils';
|
import { refreshAccessToken } from './tokenUtils';
|
||||||
|
|
||||||
export const attachAuthInterceptors = (axiosInstance) => {
|
export const attachAuthInterceptors = (axiosInstance, logout) => {
|
||||||
// 요청 시 access token 자동 첨부
|
|
||||||
axiosInstance.interceptors.request.use(config => {
|
axiosInstance.interceptors.request.use(config => {
|
||||||
const token = localStorage.getItem('access');
|
const token = localStorage.getItem('access');
|
||||||
if (token) {
|
if (token) {
|
||||||
@ -10,7 +10,6 @@ export const attachAuthInterceptors = (axiosInstance) => {
|
|||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 응답 시 access 만료 → refresh 재시도
|
|
||||||
axiosInstance.interceptors.response.use(
|
axiosInstance.interceptors.response.use(
|
||||||
res => res,
|
res => res,
|
||||||
async err => {
|
async err => {
|
||||||
@ -29,9 +28,8 @@ export const attachAuthInterceptors = (axiosInstance) => {
|
|||||||
return axiosInstance(originalRequest);
|
return axiosInstance(originalRequest);
|
||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
console.error('토큰 갱신 실패', refreshError);
|
console.error('토큰 갱신 실패', refreshError);
|
||||||
localStorage.removeItem('access');
|
if (logout) logout(); // ✅ Context logout 반영
|
||||||
localStorage.removeItem('refresh');
|
window.location.href = '/login';
|
||||||
window.location.href = '/login'; // 또는 useNavigate 사용 가능
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
src/components/Layout/Layout.js
Normal file
16
src/components/Layout/Layout.js
Normal 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;
|
@ -1,59 +1,57 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate, useLocation } from "react-router-dom";
|
||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
|
|
||||||
const Navbar = () => {
|
const Navbar = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const { isLoggedIn, logout, user } = useAuth();
|
const { isLoggedIn, logout, user } = useAuth();
|
||||||
|
|
||||||
// 로그인 상태에 따라 메뉴 구성
|
const isHome = location.pathname === "/";
|
||||||
|
|
||||||
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" },
|
{ 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 (
|
return (
|
||||||
<nav className="fixed w-full z-50 bg-white/90 backdrop-blur-md shadow-sm">
|
<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">
|
<div className="container mx-auto px-4 py-3 flex justify-between items-center">
|
||||||
<div className="flex justify-between items-center h-16">
|
<Link to="/" className="text-xl font-bold tracking-wide">
|
||||||
<Link to="/" className="text-2xl font-bold text-[#3B82F6]">
|
ICURFER
|
||||||
Dev
|
|
||||||
</Link>
|
</Link>
|
||||||
|
<div className="hidden md:flex space-x-6 font-semibold">
|
||||||
<div className="hidden md:flex items-center space-x-8">
|
|
||||||
{menuItems.map((item) => (
|
{menuItems.map((item) => (
|
||||||
<Link
|
<Link
|
||||||
key={item.name}
|
key={item.name}
|
||||||
to={item.path}
|
to={item.path}
|
||||||
className="text-gray-600 hover:text-[#D4AF37]"
|
className={`hover:text-yellow-400 transition capitalize`}
|
||||||
>
|
>
|
||||||
{item.name.charAt(0).toUpperCase() + item.name.slice(1)}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="space-x-2 flex items-center ">
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
to="/profile"
|
to="/profile"
|
||||||
className="bg-[#8b0000] text-white px-4 py-2 rounded hover:bg-gray-300"
|
className={`px-4 py-2 rounded-full shadow ${isHome ? "bg-white text-black" : "bg-gray-200 text-gray-800"}`}
|
||||||
>
|
>
|
||||||
내 정보 {user?.grade && `(${user.grade})`}
|
내 정보 | 등급: {user?.grade && `(${user.grade})`}
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
logout();
|
logout();
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
}}
|
}}
|
||||||
className="bg-gray-200 text-gray-700 px-4 py-2 rounded hover:bg-gray-300"
|
className={`px-4 py-2 rounded-full ${isHome ? "bg-white text-black" : "bg-gray-200 text-gray-800"} hover:bg-gray-300`}
|
||||||
>
|
>
|
||||||
로그아웃
|
로그아웃
|
||||||
</button>
|
</button>
|
||||||
@ -62,13 +60,13 @@ const Navbar = () => {
|
|||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
to="/login"
|
||||||
className="bg-[#3B82F6] text-white px-4 py-2 rounded hover:bg-blue-700"
|
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>
|
||||||
<Link
|
<Link
|
||||||
to="/register"
|
to="/register"
|
||||||
className="bg-gray-200 text-gray-700 px-4 py-2 rounded hover:bg-gray-300"
|
className={`px-4 py-2 rounded-full ${isHome ? "bg-gray-700 text-white hover:bg-gray-300" : "bg-gray-200 text-gray-800"}`}
|
||||||
>
|
>
|
||||||
회원가입
|
회원가입
|
||||||
</Link>
|
</Link>
|
||||||
@ -76,7 +74,6 @@ const Navbar = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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 (
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
// src/index.js
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import { AuthProvider } from './context/AuthContext'; // ✅ 추가
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<AuthProvider> {/* ✅ 전역 로그인 상태 관리 */}
|
||||||
<App />
|
<App />
|
||||||
|
</AuthProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
@ -10,10 +10,10 @@ const About = () => {
|
|||||||
animation: false,
|
animation: false,
|
||||||
radar: {
|
radar: {
|
||||||
indicator: [
|
indicator: [
|
||||||
{ name: '클라우드', max: 100 },
|
{ name: '클라우드', max: 120 },
|
||||||
{ name: '리눅스', max: 100 },
|
{ name: '리눅스(우분투)', max: 130 },
|
||||||
{ name: '데브옵스', max: 100 },
|
{ name: '쿠버네티스', max: 110 },
|
||||||
{ name: 'CI/CD', max: 100 },
|
{ name: 'CI/CD', max: 120 },
|
||||||
{ name: '기타', max: 100 }
|
{ name: '기타', max: 100 }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -39,18 +39,26 @@ const About = () => {
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 mb-20">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 mb-20">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl font-bold mb-6">우리의 비전</h3>
|
<h3 className="text-2xl font-bold mb-6">우리의 비전</h3>
|
||||||
<p className="text-gray-600 leading-relaxed">
|
<ul className="space-y-4">
|
||||||
엔지니어에게 필요한 관리 포탈을 만듭니다.
|
<li className="flex items-center">
|
||||||
</p>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl font-bold mb-6">핵심 가치</h3>
|
<h3 className="text-2xl font-bold mb-6">핵심 가치</h3>
|
||||||
<ul className="space-y-4">
|
<ul className="space-y-4">
|
||||||
<li className="flex items-center">
|
<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>
|
||||||
<li className="flex items-center">
|
<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>
|
||||||
<li className="flex items-center">
|
<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>
|
||||||
@ -59,7 +67,7 @@ const About = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-20">
|
<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 ref={chartRef} className="w-full h-[400px]"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
@ -13,7 +13,7 @@ const Board = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
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="container mx-auto px-4">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
|
@ -1,60 +1,81 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
// src/pages/Home.js
|
||||||
import blogApi from '../api/blogApi';
|
import React, { useEffect, useState } from "react";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import blogApi from "../api/blogApi";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const [posts, setPosts] = useState([]);
|
const [posts, setPosts] = useState([]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
blogApi.get('/api/blog/posts/')
|
blogApi
|
||||||
.then(res => setPosts(res.data))
|
.get("/api/blog/posts/")
|
||||||
.catch(err => console.error('게시글 목록 조회 실패:', err));
|
.then((res) => setPosts(res.data))
|
||||||
|
.catch((err) => console.error("게시글 목록 조회 실패:", err));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen">
|
<div className="min-h-screen bg-white">
|
||||||
<div className="relative h-screen">
|
{/* Hero Section */}
|
||||||
<div className="absolute inset-0">
|
<section className="relative h-[80vh]">
|
||||||
<img
|
<img
|
||||||
src="https://public.readdy.ai/ai/img_res/41c06ed594c02f95a02361b098edd073.jpg"
|
src="/bg.png"
|
||||||
className="w-full h-full object-cover"
|
|
||||||
alt="Hero Background"
|
alt="Hero Background"
|
||||||
|
className="absolute inset-0 w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
<div className="absolute inset-0 bg-gradient-to-r from-[#000000cc] to-[#00000000]" />
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-black/70 to-transparent">
|
<div className="relative z-10 flex items-center justify-start h-full container mx-auto px-4">
|
||||||
<div className="container mx-auto px-4 h-full flex items-center">
|
<div className="text-white max-w-xl">
|
||||||
<div className="max-w-2xl text-white">
|
<h1 className="text-5xl font-bold mb-6">
|
||||||
<h1 className="text-5xl font-bold mb-6">System Management Portal</h1>
|
IT Education <br />
|
||||||
<p className="text-xl mb-8">데모 사이트 입니다.</p>
|
Basic & Advanced
|
||||||
<button className="bg-[#3B82F6] text-white px-8 py-3 rounded-lg">개발 중 입니다.</button>
|
</h1>
|
||||||
</div>
|
<p className="text-lg mb-6">
|
||||||
</div>
|
Ubuntu Linux, Ansible, Docker, Kubernetes, etc.
|
||||||
</div>
|
</p>
|
||||||
</div>
|
<br/>
|
||||||
|
<h1 className="text-5xl font-bold mb-6">
|
||||||
|
System Management<br />
|
||||||
|
|
||||||
<div className="container mx-auto px-4 py-20">
|
</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"
|
||||||
|
>
|
||||||
|
자세히 보기 >
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Posts Section */}
|
||||||
|
<section className="container mx-auto px-4 py-20">
|
||||||
<h2 className="text-3xl font-bold mb-12 text-center">최신 포스트</h2>
|
<h2 className="text-3xl font-bold mb-12 text-center">최신 포스트</h2>
|
||||||
<ul className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<ul className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
{posts.length > 0 ? (
|
{posts.length > 0 ? (
|
||||||
posts.map(post => (
|
posts.map((post) => (
|
||||||
<li
|
<li
|
||||||
key={post.id}
|
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}`)}
|
onClick={() => navigate(`/posts/${post.id}`)}
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-semibold">{post.title}</h2>
|
<h3 className="text-xl font-semibold mb-2">{post.title}</h3>
|
||||||
<p className="text-gray-600">작성자: {post.author_name}</p>
|
<p className="text-gray-700 mb-1">작성자: {post.author_name}</p>
|
||||||
<p className="text-gray-400 text-sm">
|
<p className="text-gray-400 text-sm">
|
||||||
작성일: {new Date(post.created_at).toLocaleString()}
|
{new Date(post.created_at).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="text-center col-span-3 text-gray-500">게시글이 없습니다.</p>
|
<p className="text-center col-span-3 text-gray-500">
|
||||||
|
게시글이 없습니다.
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
63
src/pages/_unused_Home.js
Normal file
63
src/pages/_unused_Home.js
Normal 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;
|
Reference in New Issue
Block a user