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)}
+ >
+
+
사용자
+
+
+

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\사용자\폴더명\다른폴더명\파일명?
+
+
+
+
+ );
+}
+
+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위
+
중첩 반복문으로 인한 성능 저하
+
+
+
+ );
+}
+
+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