init
This commit is contained in:
167
package-lock.json
generated
167
package-lock.json
generated
@ -8,14 +8,24 @@
|
||||
"name": "msa-fe",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"echarts": "^5.6.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-router-dom": "^7.5.1",
|
||||
"react-scripts": "^5.0.1",
|
||||
"swiper": "^11.2.6",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"with": "^7.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
@ -2458,6 +2468,15 @@
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",
|
||||
"integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==",
|
||||
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||
@ -4800,6 +4819,13 @@
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/assert-never": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz",
|
||||
"integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ast-types-flow": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
|
||||
@ -5160,6 +5186,19 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-walk": {
|
||||
"version": "3.0.0-canary-5",
|
||||
"resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz",
|
||||
"integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.9.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@ -6819,6 +6858,22 @@
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
|
||||
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "5.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@ -13926,6 +13981,54 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.1.tgz",
|
||||
"integrity": "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"turbo-stream": "2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.1.tgz",
|
||||
"integrity": "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router/node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-scripts": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||
@ -14841,6 +14944,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
@ -15854,6 +15963,25 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/swiper": {
|
||||
"version": "11.2.6",
|
||||
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.6.tgz",
|
||||
"integrity": "sha512-8aXpYKtjy3DjcbzZfz+/OX/GhcU5h+looA6PbAzHMZT6ESSycSp9nAjPCenczgJyslV+rUGse64LMGpWE3PX9Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/swiperjs"
|
||||
},
|
||||
{
|
||||
"type": "open_collective",
|
||||
"url": "http://opencollective.com/swiper"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
@ -16219,6 +16347,12 @@
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/turbo-stream": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
||||
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@ -17057,6 +17191,22 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/with": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz",
|
||||
"integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.9.6",
|
||||
"@babel/types": "^7.9.6",
|
||||
"assert-never": "^1.2.1",
|
||||
"babel-walk": "3.0.0-canary-5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
@ -17537,6 +17687,21 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
|
||||
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||
"license": "0BSD"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
package.json
12
package.json
@ -3,13 +3,17 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"echarts": "^5.6.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-router-dom": "^7.5.1",
|
||||
"react-scripts": "^5.0.1",
|
||||
"swiper": "^11.2.6",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
@ -35,5 +39,11 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"with": "^7.0.2"
|
||||
}
|
||||
}
|
||||
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
44
src/App.js
44
src/App.js
@ -1,25 +1,31 @@
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
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 PostCategory from './pages/PostCategory';
|
||||
import Login from './pages/Login';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
<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="/board" element={<Board />} />
|
||||
<Route path="/post/:category" element={<PostCategory />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
</Routes>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
47
src/components/Footer.js
Normal file
47
src/components/Footer.js
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const Footer = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<footer className="bg-gray-900 text-white mt-auto">
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-4">TechEdu</h3>
|
||||
<p className="text-gray-400">최고의 IT 교육 플랫폼으로 당신의 커리어를 성장시키세요.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold mb-4">Quick Links</h4>
|
||||
<ul className="space-y-2">
|
||||
<li><button onClick={() => navigate('/')} className="text-gray-400 hover:text-white">Home</button></li>
|
||||
<li><button onClick={() => navigate('/about')} className="text-gray-400 hover:text-white">About</button></li>
|
||||
<li><button onClick={() => navigate('/board')} className="text-gray-400 hover:text-white">Board</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold mb-4">Categories</h4>
|
||||
<ul className="space-y-2">
|
||||
<li><button onClick={() => navigate('/post/developer')} className="text-gray-400 hover:text-white">Developer</button></li>
|
||||
<li><button onClick={() => navigate('/post/systemengineer')} className="text-gray-400 hover:text-white">System Engineer</button></li>
|
||||
<li><button onClick={() => navigate('/post/etc')} className="text-gray-400 hover:text-white">Other Posts</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold mb-4">Contact</h4>
|
||||
<ul className="space-y-2 text-gray-400">
|
||||
<li className="flex items-center"><i className="fas fa-envelope mr-2"></i> contact@techedu.com</li>
|
||||
<li className="flex items-center"><i className="fas fa-phone mr-2"></i> 02-1234-5678</li>
|
||||
<li className="flex items-center"><i className="fas fa-map-marker-alt mr-2"></i> 서울특별시 강남구 테헤란로 123</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-gray-800 mt-8 pt-8 text-center text-gray-400">
|
||||
<p>© 2025 TechEdu. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
57
src/components/Navbar.js
Normal file
57
src/components/Navbar.js
Normal file
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
const Navbar = () => {
|
||||
const menuItems = ['home', 'about', 'board', 'etc'];
|
||||
const postCategories = ['developer', 'systemengineer', 'etc'];
|
||||
const navigate = useNavigate();
|
||||
|
||||
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]">TechEdu</Link>
|
||||
<div className="hidden md:flex items-center space-x-8">
|
||||
{menuItems.map((item) => (
|
||||
<Link
|
||||
key={item}
|
||||
to={item === 'home' ? '/' : `/${item}`}
|
||||
className="text-gray-600 hover:text-[#D4AF37]"
|
||||
>
|
||||
{item.charAt(0).toUpperCase() + item.slice(1)}
|
||||
</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>
|
||||
|
||||
{/* ✅ 로그인 버튼 */}
|
||||
<Link
|
||||
to="/login"
|
||||
className="bg-[#3B82F6] text-white px-4 py-2 rounded hover:bg-blue-700 transition"
|
||||
>
|
||||
로그인
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
19
src/components/PostCard.js
Normal file
19
src/components/PostCard.js
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
const PostCard = ({ post }) => {
|
||||
return (
|
||||
<div className="bg-white shadow-lg rounded-lg overflow-hidden transition-transform duration-300 hover:-translate-y-2">
|
||||
<img src={post.image} alt={post.title} className="w-full h-48 object-cover" />
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-semibold mb-2">{post.title}</h3>
|
||||
<p className="text-gray-600 mb-4">{post.content}</p>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">{post.date}</span>
|
||||
<button className="text-[#D4AF37] hover:text-[#000080]">더 보기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostCard;
|
22
src/data/sampleData.js
Normal file
22
src/data/sampleData.js
Normal file
@ -0,0 +1,22 @@
|
||||
export const posts = [
|
||||
{
|
||||
id: 1,
|
||||
title: '인공지능 기초 학습 가이드',
|
||||
content: '인공지능 학습을 시작하는 분들을 위한 완벽 가이드...',
|
||||
date: '2025-02-19',
|
||||
image: 'https://public.readdy.ai/ai/img_res/c22ebfe39e631e808ad948c508839779.jpg'
|
||||
},
|
||||
// ...
|
||||
];
|
||||
|
||||
export const boardItems = [
|
||||
{
|
||||
id: 1,
|
||||
title: '신규 프로젝트 진행 현황 보고',
|
||||
content: '2025년 1분기 신규 프로젝트의 진행 상황...',
|
||||
author: '김지원',
|
||||
updatedAt: '2025-02-19'
|
||||
},
|
||||
// ...
|
||||
];
|
||||
|
@ -11,3 +11,10 @@ code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* 추가로 FontAwesome을 위한 CSS */
|
||||
@import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||
|
@ -2,16 +2,10 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
);
|
81
src/pages/About.js
Normal file
81
src/pages/About.js
Normal file
@ -0,0 +1,81 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const About = () => {
|
||||
const chartRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const chart = echarts.init(chartRef.current);
|
||||
chart.setOption({
|
||||
animation: false,
|
||||
radar: {
|
||||
indicator: [
|
||||
{ name: '프로그래밍', max: 100 },
|
||||
{ name: '클라우드', max: 100 },
|
||||
{ name: '인공지능', max: 100 },
|
||||
{ name: '데이터 분석', max: 100 },
|
||||
{ name: '보안', max: 100 }
|
||||
]
|
||||
},
|
||||
series: [{
|
||||
type: 'radar',
|
||||
data: [{
|
||||
value: [95, 90, 85, 88, 92],
|
||||
name: '교육 역량',
|
||||
areaStyle: { color: 'rgba(59, 130, 246, 0.2)' },
|
||||
lineStyle: { color: '#3B82F6' }
|
||||
}]
|
||||
}]
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<div className="container mx-auto px-4 py-20">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold mb-6">About Us</h2>
|
||||
<p className="text-xl text-gray-600">최고의 IT 교육 플랫폼을 만들어갑니다</p>
|
||||
</div>
|
||||
<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">
|
||||
실무 중심의 IT 교육을 통해 더 많은 사람들이 디지털 시대의 전문가로
|
||||
성장할 수 있도록 돕고, 글로벌 IT 인재 양성의 허브가 되고자 합니다.
|
||||
</p>
|
||||
</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>
|
||||
</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 className="mb-20">
|
||||
<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">
|
||||
<h3 className="text-2xl font-bold mb-6">Contact Us</h3>
|
||||
<div className="space-y-4">
|
||||
<p><i className="fas fa-envelope mr-2"></i> contact@example.com</p>
|
||||
<p><i className="fas fa-phone mr-2"></i> 02-1234-5678</p>
|
||||
<p><i className="fas fa-map-marker-alt mr-2"></i> 서울특별시 강남구 테헤란로 123</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
80
src/pages/Board.js
Normal file
80
src/pages/Board.js
Normal file
@ -0,0 +1,80 @@
|
||||
import React, { useState } from 'react';
|
||||
import { boardItems } from '../data/sampleData';
|
||||
|
||||
const Board = () => {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 10;
|
||||
|
||||
const filteredItems = boardItems.filter(item =>
|
||||
item.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.author.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-20">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="검색어를 입력하세요"
|
||||
className="w-full px-4 py-3 pl-12 border-none rounded-lg bg-white shadow-md text-sm"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<i className="fas fa-search absolute left-4 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead className="bg-[#3B82F6] text-white">
|
||||
<tr>
|
||||
<th className="px-6 py-4 text-left w-20">번호</th>
|
||||
<th className="px-6 py-4 text-left">제목</th>
|
||||
<th className="px-6 py-4 text-left">내용</th>
|
||||
<th className="px-6 py-4 text-left w-32">작성자</th>
|
||||
<th className="px-6 py-4 text-left w-32">수정일</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredItems
|
||||
.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
|
||||
.map(item => (
|
||||
<tr key={item.id} className="border-b border-gray-100 hover:bg-gray-50">
|
||||
<td className="px-6 py-4">{item.id}</td>
|
||||
<td className="px-6 py-4 font-medium">{item.title}</td>
|
||||
<td className="px-6 py-4 text-gray-600">{item.content.substring(0, 30)}...</td>
|
||||
<td className="px-6 py-4">{item.author}</td>
|
||||
<td className="px-6 py-4 text-gray-500">{item.updatedAt}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-center">
|
||||
<div className="flex space-x-2">
|
||||
{Array.from({ length: Math.ceil(filteredItems.length / itemsPerPage) }, (_, i) => (
|
||||
<button
|
||||
key={i + 1}
|
||||
onClick={() => setCurrentPage(i + 1)}
|
||||
className={`px-4 py-2 rounded ${
|
||||
currentPage === i + 1
|
||||
? 'bg-[#3B82F6] text-white'
|
||||
: 'bg-white text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Board;
|
38
src/pages/Home.js
Normal file
38
src/pages/Home.js
Normal file
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { posts } from '../data/sampleData';
|
||||
import PostCard from '../components/PostCard';
|
||||
|
||||
const Home = () => {
|
||||
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">IT 교육의 새로운 기준</h1>
|
||||
<p className="text-xl mb-8">실무 중심의 IT 교육 콘텐츠로 당신의 커리어를 성장시키세요</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>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{posts.map(post => (
|
||||
<PostCard key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
58
src/pages/Login.js
Normal file
58
src/pages/Login.js
Normal file
@ -0,0 +1,58 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const Login = () => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
const response = await fetch(`${process.env.REACT_APP_API_URL}/api/auth/login/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('로그인 실패');
|
||||
|
||||
const data = await response.json();
|
||||
localStorage.setItem('access', data.access);
|
||||
localStorage.setItem('refresh', data.refresh);
|
||||
alert('로그인 성공!');
|
||||
navigate('/');
|
||||
} catch (error) {
|
||||
alert('로그인 실패: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center px-4">
|
||||
<div className="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
|
||||
<h2 className="text-2xl font-bold mb-6 text-center text-[#3B82F6]">로그인</h2>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="w-full px-4 py-2 border rounded mb-4"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full px-4 py-2 border rounded mb-6"
|
||||
/>
|
||||
<button
|
||||
onClick={handleLogin}
|
||||
className="w-full bg-[#3B82F6] text-white py-2 rounded hover:bg-blue-700"
|
||||
>
|
||||
로그인
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
25
src/pages/PostCategory.js
Normal file
25
src/pages/PostCategory.js
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { posts } from '../data/sampleData';
|
||||
import PostCard from '../components/PostCard';
|
||||
|
||||
const PostCategory = () => {
|
||||
const { category } = useParams();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-20">
|
||||
<div className="container mx-auto px-4">
|
||||
<h2 className="text-3xl font-bold mb-12 text-center">
|
||||
{category.charAt(0).toUpperCase() + category.slice(1)} 관련 포스트
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{posts.map(post => (
|
||||
<PostCard key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostCategory;
|
11
tailwind.config.js
Normal file
11
tailwind.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,jsx,ts,tsx}"
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
Reference in New Issue
Block a user