diff --git a/package-lock.json b/package-lock.json index 1ef5624..89f98ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "cofix-front", "version": "0.0.0", "dependencies": { + "@monaco-editor/react": "^4.7.0", "@tailwindcss/postcss": "^4.3.2", "react": "^19.2.7", "react-dom": "^19.2.7", @@ -552,6 +553,29 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@monaco-editor/loader": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", + "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -1507,6 +1531,14 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@vitejs/plugin-react": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", @@ -1790,6 +1822,16 @@ "node": ">=8" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.381", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.381.tgz", @@ -2640,6 +2682,19 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -2683,6 +2738,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "license": "MIT", + "peer": true, + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -3033,6 +3099,12 @@ "node": ">=0.10.0" } }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", diff --git a/package.json b/package.json index d23f058..e601ca3 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@monaco-editor/react": "^4.7.0", "@tailwindcss/postcss": "^4.3.2", "react": "^19.2.7", "react-dom": "^19.2.7", diff --git a/src/App.jsx b/src/App.jsx index 97b394d..6dc435d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,10 +1,20 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import MainLayout from './layouts/MainLayout'; +import Dashboard from './pages/Dashboard'; +import Report from './pages/Report'; +import ReviewNote from './pages/ReviewNote'; +import AiChat from './pages/AiChat'; function App() { return ( - + }> + } /> + } /> + } /> + } /> + ); diff --git a/src/assets/SettingIcon.png b/src/assets/SettingIcon.png new file mode 100644 index 0000000..e4e1a89 Binary files /dev/null and b/src/assets/SettingIcon.png differ diff --git a/src/components/IdeDisconnected.jsx b/src/components/IdeDisconnected.jsx new file mode 100644 index 0000000..b6b2261 --- /dev/null +++ b/src/components/IdeDisconnected.jsx @@ -0,0 +1,14 @@ +function IdeDisconnected() { + return ( +
+
+
+ IDE에 연결 되어 있지 않습니다. +
+
연결을 확인해 주세요.
+
+
+ ); +} + +export default IdeDisconnected; diff --git a/src/components/IdeStatus.jsx b/src/components/IdeStatus.jsx new file mode 100644 index 0000000..f219b64 --- /dev/null +++ b/src/components/IdeStatus.jsx @@ -0,0 +1,19 @@ +function IdeStatus({ isConnected = false }) { + return ( +
+
+ {isConnected ? '🟢 IDE 연동 중' : '🔴 IDE 연동 전'} +
+
+ ); +} + +export default IdeStatus; \ No newline at end of file diff --git a/src/components/NotLoggedIn.jsx b/src/components/NotLoggedIn.jsx new file mode 100644 index 0000000..9a16abd --- /dev/null +++ b/src/components/NotLoggedIn.jsx @@ -0,0 +1,17 @@ +function NotLoggedIn() { + return ( +
+
+
+ 로그인이 되어 있지 않습니다. +
+
+
로그인
+
을 진행해 주세요.
+
+
+
+ ); +} + +export default NotLoggedIn; diff --git a/src/components/SideNav/SideNav.jsx b/src/components/SideNav/SideNav.jsx new file mode 100644 index 0000000..cf07646 --- /dev/null +++ b/src/components/SideNav/SideNav.jsx @@ -0,0 +1,93 @@ +import { useState, useEffect, useRef } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import SettingIcon from "../../assets/SettingIcon.png"; +import UserModal from "./UserModal"; + +function SideNav() { + const menuItems = [ + { name: 'DASHBOARD', path: '/' }, + { name: 'REPORT', path: '/report' }, + { name: 'REVIEW NOTE', path: '/review-note' }, + { name: 'AI CHAT', path: '/ai-chat' } + ]; + + const location = useLocation(); + const navigate = useNavigate(); + + const [isModalOpen, setIsModalOpen] = useState(false); + + const modalRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event) => { + if (modalRef.current && !modalRef.current.contains(event.target)) { + setIsModalOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
+
+
CoFix
+ +
+ {menuItems.map((item) => { + const isActive = location.pathname === item.path; + + return ( + + ); + })} +
+
+ +
+ {isModalOpen && ( +
+ +
+ )} + +
setIsModalOpen(!isModalOpen)} + > +
+
사용자
+
+ + settings setIsModalOpen(!isModalOpen)} + /> +
+
+ ); +} + +export default SideNav; \ No newline at end of file diff --git a/src/components/SideNav/UserModal.jsx b/src/components/SideNav/UserModal.jsx new file mode 100644 index 0000000..bfecd26 --- /dev/null +++ b/src/components/SideNav/UserModal.jsx @@ -0,0 +1,44 @@ +import { useState } from 'react'; + +function UserModal() { + const [isDarkMode, setIsDarkMode] = useState(true); + + return ( +
+
+
+
사용자
+
+ +
setIsDarkMode(!isDarkMode)} + className="relative flex items-center w-[5vw] h-[2.96vh] mb-[0.9vh] border-[0.09vh] border-gray400 rounded-[1.04vw] cursor-pointer" + > +
+ + + +
+
+ +
+
계정관리
+

|

+
로그아웃
+
+
+ ); +} + +export default UserModal; \ No newline at end of file diff --git a/src/components/dashboard/AiCorrectionPanel.jsx b/src/components/dashboard/AiCorrectionPanel.jsx new file mode 100644 index 0000000..fa52388 --- /dev/null +++ b/src/components/dashboard/AiCorrectionPanel.jsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { DiffEditor } from '@monaco-editor/react'; + +// ------------ 임시 ------------ +const myCode = `import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +export const useLogin = () => { + const navigate = useNavigate(); + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [emailError, setEmailError] = useState(''); + const [passwordError, setPasswordError] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); +};`; + +const aiCleanCode = `import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +export const useLogin = () => { + const navigate = useNavigate(); + + // AI 가 제안하는 최적화: 상태 병합 + const [credentials, setCredentials] = useState({ + email: '', + password: '', + }); + + const [errors, setErrors] = useState({ + email: '', + password: '', + global: '', + }); + +};`; +// ------------------------------ + +// 에디터 공통 옵션 +const editorOptions = { + readOnly: true, + minimap: { enabled: false }, + scrollBeyondLastLine: false, + fontSize: 12, + lineHeight: "1.5vh", + renderOverviewRuler: false, + overviewRulerBorder: false, + stickyScroll: { enabled: false }, + scrollbar: { useShadows: false } +}; + +function AiCodeDiffViewer() { + const handleEditorMount = (editor) => { + editor.getOriginalEditor().updateOptions({ + scrollbar: { + vertical: 'hidden', + horizontal: 'hidden' + } + }); + }; + + return ( +
+
내 코드 vs AI 클린 코드
+ +
+
+ +
+
+ +
+ ); +} + +export default AiCodeDiffViewer; \ No newline at end of file diff --git a/src/components/dashboard/CodingSummary.jsx b/src/components/dashboard/CodingSummary.jsx new file mode 100644 index 0000000..56a64f2 --- /dev/null +++ b/src/components/dashboard/CodingSummary.jsx @@ -0,0 +1,31 @@ +function CodingSummary() { + return ( +
+
실시간 코딩 요약
+ +
+
현재 파일 위치
+
C:\Users\사용자\폴더명\다른폴더명\파일명?
+
+ +
+
+
누적 코딩 시간
+
2h 15m
+
+
|
+
+
현재 타이핑
+
65 WPM
+
+
|
+
+
감지된 약점
+
2건
+
+
+
+ ); +} + +export default CodingSummary; \ No newline at end of file diff --git a/src/components/dashboard/CustomQuiz/CustomQuiz.jsx b/src/components/dashboard/CustomQuiz/CustomQuiz.jsx new file mode 100644 index 0000000..3cb418a --- /dev/null +++ b/src/components/dashboard/CustomQuiz/CustomQuiz.jsx @@ -0,0 +1,14 @@ +function CustomQuiz() { + return ( +
+
실시간 맞춤 퀴즈
+
+

감지된 취약점과 관련된 최적화 퀴즈가 준비되었습니다.

+

실시간 피드백으로 보안 역량을 강화하세요.

+
+
퀴즈 풀기
+
+ ); +} + +export default CustomQuiz; \ No newline at end of file diff --git a/src/assets/.gitkeep b/src/components/dashboard/CustomQuiz/CustomQuizLogic.js similarity index 100% rename from src/assets/.gitkeep rename to src/components/dashboard/CustomQuiz/CustomQuizLogic.js diff --git a/src/components/.gitkeep b/src/components/dashboard/CustomQuiz/CustomQuizModal.jsx similarity index 100% rename from src/components/.gitkeep rename to src/components/dashboard/CustomQuiz/CustomQuizModal.jsx diff --git a/src/components/dashboard/VulnerabilityTop2.jsx b/src/components/dashboard/VulnerabilityTop2.jsx new file mode 100644 index 0000000..580a185 --- /dev/null +++ b/src/components/dashboard/VulnerabilityTop2.jsx @@ -0,0 +1,17 @@ +function VulnerabilityTop2() { + return ( +
+
AI 감지 현재 취약점 TOP 2
+
+
1위
+
중첩 반복문으로 인한 성능 저하
+
+
+
2위
+
변수명 컨벤션 불일치
+
+
+ ); +} + +export default VulnerabilityTop2; \ No newline at end of file diff --git a/src/index.css b/src/index.css index 534a63a..c149e71 100644 --- a/src/index.css +++ b/src/index.css @@ -2,9 +2,32 @@ @theme { --color-background: #0F1115; - --color-gray400: #9CA3AF; + --color-gray900: rgb(31 31 31); + --color-gray800-50: rgb(31 41 55 / 50%); + --color-gray700: rgb(55 65 81); + --color-gray400: rgb(156 163 175); + --color-gray200: rgb(229 231 235); + --color-white-5: rgb(255 255 255 / 5%); --color-white-3: rgb(255 255 255 / 3%); + + --color-purple500: rgb(143 57 248); + --color-purple500-20: rgb(143 57 248 / 20%); + --color-purple500-10: rgb(143 57 248 / 10%); + --color-purple400: rgb(158 134 245); + + --color-green500-30: rgb(34 197 94 / 30%); + --color-green500-20: rgb(34 197 94 / 20%); + --color-green500-10: rgb(34 197 94 / 10%); + --color-green400: rgb(74 222 128); + + --color-red500-30: rgb(239 68 68 / 30%); + --color-red500-20: rgb(239 68 68 / 20%); + --color-red500-10: rgb(239 68 68 / 10%); + --color-red400: rgb(248 113 113); + + --color-yellow400: rgb(250 204 21); + --font-sans: "Pretendard", sans-serif; } @@ -65,13 +88,16 @@ @layer base { html, body { - @apply bg-background text-gray400; + @apply flex w-screen h-screen bg-background text-gray400; } } @layer components { .panel-base { - @apply flex flex-col items-center bg-white-3 border border-white-5 rounded-[5vh]; + @apply flex flex-col items-center bg-white-3 border-[0.09vh] border-white-5 rounded-[1.04vw]; + } + .panel-base:not(.no-hover):hover { + border-color: var(--color-purple400); } .panel-title { @apply text-[2.5vh] font-bold text-white; diff --git a/src/layouts/MainLayout.jsx b/src/layouts/MainLayout.jsx new file mode 100644 index 0000000..b452872 --- /dev/null +++ b/src/layouts/MainLayout.jsx @@ -0,0 +1,31 @@ +import { Outlet } from 'react-router-dom'; +import { useState } from 'react'; +import SideNav from '../components/SideNav/SideNav'; +import IdeStatus from '../components/IdeStatus'; +import IdeDisconnected from '../components/IdeDisconnected'; +import NotLoggedIn from '../components/NotLoggedIn'; + +export default function MainLayout() { + const [isConnected, setIsConnected] = useState(true); + const [isLoggedIn, setIsLoggedIn] = useState(false); + + return ( +
+ + +
+ + +
+ {!isConnected ? ( + + ) : !isLoggedIn ? ( + + ) : ( + + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/pages/.gitkeep b/src/pages/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/pages/AiChat.jsx b/src/pages/AiChat.jsx new file mode 100644 index 0000000..de9047b --- /dev/null +++ b/src/pages/AiChat.jsx @@ -0,0 +1,7 @@ +function AiChat() { + return ( + <>Ai Chat + ); +} + +export default AiChat; \ No newline at end of file diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx new file mode 100644 index 0000000..371542e --- /dev/null +++ b/src/pages/Dashboard.jsx @@ -0,0 +1,19 @@ +import CodingSummary from '../components/dashboard/CodingSummary'; +import CodeComparison from '../components/dashboard/AiCorrectionPanel'; +import VulnerabilityTop2 from '../components/dashboard/VulnerabilityTop2'; +import CustomQuiz from '../components/dashboard/CustomQuiz/CustomQuiz'; + +function Dashboard() { + return ( +
+ + +
+ + +
+
+ ) +} + +export default Dashboard; \ No newline at end of file diff --git a/src/pages/Report.jsx b/src/pages/Report.jsx new file mode 100644 index 0000000..057f3aa --- /dev/null +++ b/src/pages/Report.jsx @@ -0,0 +1,7 @@ +function Report() { + return ( + <>Report + ); +} + +export default Report; \ No newline at end of file diff --git a/src/pages/ReviewNote.jsx b/src/pages/ReviewNote.jsx new file mode 100644 index 0000000..ecada77 --- /dev/null +++ b/src/pages/ReviewNote.jsx @@ -0,0 +1,7 @@ +function ReviewNote() { + return ( + <>Review Note + ); +} + +export default ReviewNote; \ No newline at end of file