diff --git a/package-lock.json b/package-lock.json index 1f41019..4cb05c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" } } } diff --git a/package.json b/package.json index 5f54bc4..ec03503 100644 --- a/package.json +++ b/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" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/src/App.js b/src/App.js index 3784575..5983596 100644 --- a/src/App.js +++ b/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 ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ +
+ +
+ + } /> + } /> + } /> + } /> + } /> + +
+
+
); } -export default App; +export default App; \ No newline at end of file diff --git a/src/components/Footer.js b/src/components/Footer.js new file mode 100644 index 0000000..c160e81 --- /dev/null +++ b/src/components/Footer.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +const Footer = () => { + const navigate = useNavigate(); + return ( + + ); +}; + +export default Footer; \ No newline at end of file diff --git a/src/components/Navbar.js b/src/components/Navbar.js new file mode 100644 index 0000000..ddd6d9e --- /dev/null +++ b/src/components/Navbar.js @@ -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 ( + + ); +}; + +export default Navbar; diff --git a/src/components/PostCard.js b/src/components/PostCard.js new file mode 100644 index 0000000..8b98f8b --- /dev/null +++ b/src/components/PostCard.js @@ -0,0 +1,19 @@ +import React from 'react'; + +const PostCard = ({ post }) => { + return ( +
+ {post.title} +
+

{post.title}

+

{post.content}

+
+ {post.date} + +
+
+
+ ); +}; + +export default PostCard; \ No newline at end of file diff --git a/src/data/sampleData.js b/src/data/sampleData.js new file mode 100644 index 0000000..b37a1da --- /dev/null +++ b/src/data/sampleData.js @@ -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' + }, + // ... + ]; + \ No newline at end of file diff --git a/src/index.css b/src/index.css index ec2585e..b3a7fe5 100644 --- a/src/index.css +++ b/src/index.css @@ -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'; diff --git a/src/index.js b/src/index.js index d563c0f..1675893 100644 --- a/src/index.js +++ b/src/index.js @@ -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( -); - -// 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(); +); \ No newline at end of file diff --git a/src/pages/About.js b/src/pages/About.js new file mode 100644 index 0000000..3bc0ee1 --- /dev/null +++ b/src/pages/About.js @@ -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 ( +
+
+
+
+

About Us

+

최고의 IT 교육 플랫폼을 만들어갑니다

+
+
+
+

우리의 비전

+

+ 실무 중심의 IT 교육을 통해 더 많은 사람들이 디지털 시대의 전문가로 + 성장할 수 있도록 돕고, 글로벌 IT 인재 양성의 허브가 되고자 합니다. +

+
+
+

핵심 가치

+
    +
  • + 실무 중심 교육 +
  • +
  • + 최신 기술 트렌드 +
  • +
  • + 커뮤니티 중심 +
  • +
+
+
+
+

전문 역량

+
+
+
+

Contact Us

+
+

contact@example.com

+

02-1234-5678

+

서울특별시 강남구 테헤란로 123

+
+
+
+
+
+ ); +}; + +export default About; \ No newline at end of file diff --git a/src/pages/Board.js b/src/pages/Board.js new file mode 100644 index 0000000..f4c4108 --- /dev/null +++ b/src/pages/Board.js @@ -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 ( +
+
+
+
+
+ setSearchTerm(e.target.value)} + /> + +
+
+
+ + + + + + + + + + + + {filteredItems + .slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage) + .map(item => ( + + + + + + + + ))} + +
번호제목내용작성자수정일
{item.id}{item.title}{item.content.substring(0, 30)}...{item.author}{item.updatedAt}
+
+
+
+ {Array.from({ length: Math.ceil(filteredItems.length / itemsPerPage) }, (_, i) => ( + + ))} +
+
+
+
+
+ ); +}; + +export default Board; \ No newline at end of file diff --git a/src/pages/Home.js b/src/pages/Home.js new file mode 100644 index 0000000..a62c3f2 --- /dev/null +++ b/src/pages/Home.js @@ -0,0 +1,38 @@ +import React from 'react'; +import { posts } from '../data/sampleData'; +import PostCard from '../components/PostCard'; + +const Home = () => { + return ( +
+
+
+ Hero Background +
+
+
+
+

IT 교육의 새로운 기준

+

실무 중심의 IT 교육 콘텐츠로 당신의 커리어를 성장시키세요

+ +
+
+
+
+
+

최신 포스트

+
+ {posts.map(post => ( + + ))} +
+
+
+ ); +}; + +export default Home; \ No newline at end of file diff --git a/src/pages/Login.js b/src/pages/Login.js new file mode 100644 index 0000000..46ca205 --- /dev/null +++ b/src/pages/Login.js @@ -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 ( +
+
+

로그인

+ setUsername(e.target.value)} + className="w-full px-4 py-2 border rounded mb-4" + /> + setPassword(e.target.value)} + className="w-full px-4 py-2 border rounded mb-6" + /> + +
+
+ ); +}; + +export default Login; diff --git a/src/pages/PostCategory.js b/src/pages/PostCategory.js new file mode 100644 index 0000000..bc1555e --- /dev/null +++ b/src/pages/PostCategory.js @@ -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 ( +
+
+

+ {category.charAt(0).toUpperCase() + category.slice(1)} 관련 포스트 +

+
+ {posts.map(post => ( + + ))} +
+
+
+ ); +}; + +export default PostCategory; \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..cbbc393 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{js,jsx,ts,tsx}" + ], + theme: { + extend: {}, + }, + plugins: [], +} +