diff --git a/src/api/reviewService.js b/src/api/reviewService.js index ea2b8ed..3f73cd1 100644 --- a/src/api/reviewService.js +++ b/src/api/reviewService.js @@ -1,55 +1,51 @@ // src/api/reviewService.js - -// 중요: 백엔드 주소를 정확하게 입력 (Proxy 설정이 없다면 전체 주소 필수) const BASE_URL = "http://localhost:8080/api"; -export const fetchCodeReview = async (code, comment) => { - // 1. 데이터 객체 생성 +export const fetchCodeReview = async (code, comment, repoUrl) => { const payload = { code: code, comment: comment && comment.trim() ? comment.trim() : null, + repoUrl: repoUrl && repoUrl.trim() ? repoUrl.trim() : null, }; try { - // 2. fetch 요청 (JSON 모드) const res = await fetch(`${BASE_URL}/review`, { method: "POST", headers: { - "Content-Type": "application/json", // 나 JSON 보낸다고 알려줌 + "Content-Type": "application/json", }, - body: JSON.stringify(payload), // 객체를 문자열로 변환 + body: JSON.stringify(payload), }); - // 3. 에러 처리 const raw = await res.text(); - + if (!res.ok) { - // 서버가 에러 응답을 준 경우 - let errMsg = raw; try { - const json = JSON.parse(raw); - errMsg = json.message || json.error || json.detail || raw; + const errJson = JSON.parse(raw); + throw new Error( + errJson.message || + errJson.error || + `코드 리뷰 요청 실패 (status: ${res.status})` + ); } catch { - // JSON 파싱 실패 시 raw text 사용 + throw new Error( + raw || `코드 리뷰 요청 실패 (status: ${res.status})` + ); } - throw new Error(errMsg || `요청 실패 (${res.status})`); } - if (!raw) return {}; - - // 정상 응답 파싱 try { return JSON.parse(raw); } catch { return { review: raw, questions: [] }; } - } catch (error) { console.error("API 요청 실패:", error); - // "Failed to fetch"는 보통 서버가 꺼져있거나 주소가 틀렸을 때 발생 if (error.message === "Failed to fetch") { - throw new Error("서버에 연결할 수 없습니다. 백엔드 서버가 켜져 있는지 확인해주세요."); + throw new Error( + "서버에 연결할 수 없습니다. 백엔드 서버가 켜져 있는지 확인해주세요." + ); } throw error; } -}; \ No newline at end of file +}; diff --git a/src/features/codingTest/CodingTest.jsx b/src/features/codingTest/CodingTest.jsx index 7aafd00..393bf32 100644 --- a/src/features/codingTest/CodingTest.jsx +++ b/src/features/codingTest/CodingTest.jsx @@ -4,7 +4,6 @@ import { Link } from "react-router-dom"; import { Sparkles, Send, - Play, CheckCircle2, XCircle, Terminal, @@ -30,7 +29,9 @@ const fetchRandomProblem = async (difficulty) => { if (!response.ok) { const text = await response.text().catch(() => ""); - throw new Error(text || `랜덤 문제를 불러오지 못했습니다. (status: ${response.status})`); + throw new Error( + text || `랜덤 문제를 불러오지 못했습니다. (status: ${response.status})` + ); } return await response.json(); @@ -41,7 +42,7 @@ const submitCode = async ({ problemId, code, language, userId }) => { problemId, sourceCode: code, language, - userId: userId ?? 1 // userId가 null/undefined일 경우 기본값 1 사용 (Long 타입 일치) + userId: userId ?? 1, // userId가 null/undefined일 경우 기본값 1 사용 (Long 타입 일치) }; // API 경로: /api/coding/submissions @@ -65,12 +66,26 @@ const submitCode = async ({ problemId, code, language, userId }) => { // 컴포넌트 시작 // ----------------------------------------------------------- - // 언어 옵션 const LANGUAGE_OPTIONS = [ - { value: "python", label: "Python", color: "text-blue-400", activeBorder: "border-blue-400/60 bg-blue-500/10" }, - { value: "java", label: "Java", color: "text-orange-400", activeBorder: "border-orange-400/60 bg-orange-500/10" }, - { value: "cpp", label: "C++", color: "text-purple-400", activeBorder: "border-purple-400/60 bg-purple-500/10" }, + { + value: "python", + label: "Python", + color: "text-blue-400", + activeBorder: "border-blue-400/60 bg-blue-500/10", + }, + { + value: "java", + label: "Java", + color: "text-orange-400", + activeBorder: "border-orange-400/60 bg-orange-500/10", + }, + { + value: "cpp", + label: "C++", + color: "text-purple-400", + activeBorder: "border-purple-400/60 bg-purple-500/10", + }, ]; // 기본 템플릿 @@ -109,9 +124,21 @@ int main() { // 난이도 색 const DIFFICULTY_CONFIG = { - EASY: { label: "쉬움", color: "text-emerald-300 bg-emerald-500/20 border-emerald-400/30 shadow-[0_0_15px_rgba(52,211,153,0.15)]" }, - MEDIUM: { label: "보통", color: "text-amber-300 bg-amber-500/20 border-amber-400/30 shadow-[0_0_15px_rgba(251,191,36,0.15)]" }, - HARD: { label: "어려움", color: "text-rose-300 bg-rose-500/20 border-rose-400/30 shadow-[0_0_15px_rgba(251,113,133,0.15)]" }, + EASY: { + label: "쉬움", + color: + "text-emerald-300 bg-emerald-500/20 border-emerald-400/30 shadow-[0_0_15px_rgba(52,211,153,0.15)]", + }, + MEDIUM: { + label: "보통", + color: + "text-amber-300 bg-amber-500/20 border-amber-400/30 shadow-[0_0_15px_rgba(251,191,36,0.15)]", + }, + HARD: { + label: "어려움", + color: + "text-rose-300 bg-rose-500/20 border-rose-400/30 shadow-[0_0_15px_rgba(251,113,133,0.15)]", + }, }; export default function CodingTest() { @@ -124,16 +151,17 @@ export default function CodingTest() { const [result, setResult] = useState(null); const [errorMsg, setErrorMsg] = useState(""); - // AI 피드백(리뷰/면접질문) 구역을 열지 말지 토글 - const [showFeedback, setShowFeedback] = useState(false); - // 코드 리뷰 vs 예상 면접 질문 토글 상태 const [showInterview, setShowInterview] = useState(false); // 언어 변경 const handleChangeLanguage = (nextLang) => { if (code !== LANGUAGE_TEMPLATES[language] && code.trim() !== "") { - if (!window.confirm("언어를 변경하면 작성 중인 코드가 초기화됩니다. 계속하시겠습니까?")) { + if ( + !window.confirm( + "언어를 변경하면 작성 중인 코드가 초기화됩니다. 계속하시겠습니까?" + ) + ) { return; } } @@ -146,8 +174,8 @@ export default function CodingTest() { setIsLoadingProblem(true); setErrorMsg(""); setResult(null); - setShowFeedback(false); setShowInterview(false); + try { const data = await fetchRandomProblem(difficulty); setProblem(data); @@ -174,7 +202,6 @@ export default function CodingTest() { setIsSubmitting(true); setErrorMsg(""); setResult(null); - setShowFeedback(false); setShowInterview(false); try { @@ -185,17 +212,9 @@ export default function CodingTest() { userId: 1, // Long 타입이므로 숫자 1 사용 }); setResult(res); - - // aiFeedback 또는 interviewQuestions가 있으면 피드백 영역 기본 ON - if (res.aiFeedback || (res.interviewQuestions && res.interviewQuestions.length > 0)) { - setShowFeedback(true); - } else { - setShowFeedback(false); - } + // showInterview는 기본 false (코드 리뷰 먼저 보여줌) } catch (err) { - setErrorMsg( - err?.message || "채점 서버 통신 중 오류가 발생했습니다." - ); + setErrorMsg(err?.message || "채점 서버 통신 중 오류가 발생했습니다."); } finally { setIsSubmitting(false); } @@ -205,29 +224,37 @@ export default function CodingTest() { return ["AC", "SUCCESS", "PASSED"].includes(status?.toUpperCase()); }; + const hasFeedback = + !!result?.aiFeedback || + (Array.isArray(result?.interviewQuestions) && + result.interviewQuestions.length > 0); + return ( // 배경: 딥 블루 그라데이션
- {/* 배경 조명 효과 */}
-
+
{/* 헤더 */}
- {/* 왼쪽: Home 버튼 */}
- - Home + + + Home +
@@ -274,45 +301,56 @@ export default function CodingTest() { {isLoadingProblem ? ( ) : ( - + )} - {isLoadingProblem ? "생성 중..." : "문제 생성"} + + {isLoadingProblem ? "생성 중..." : "문제 생성"} +
{/* 메인 컨텐츠 영역 */}
- {/* 에러 알림 */} {errorMsg && (
- +

{errorMsg}

)} {/* 메인 작업 영역 (Grid) */}
- {/* 왼쪽: 문제 설명 패널 */}
- - {/* 패널 헤더 (고정) */} + {/* 패널 헤더 (고정) */}
- 문제 설명 + + 문제 설명 +
{problem && ( - + {DIFFICULTY_CONFIG[problem.difficulty].label} )}
{/* 컨텐츠 영역 (여기가 스크롤됨) */} -
+
{isLoadingProblem ? ( // 로딩 스켈레톤
@@ -330,34 +368,45 @@ export default function CodingTest() {

{problem.title}

-
+
-
{problem.description}
+
+ {problem.description} +
{problem.samples && problem.samples.length > 0 && (

- + 예시 입력/출력

{problem.samples.map((sample, idx) => ( -
+
- Case #{idx + 1} + + Case #{idx + 1} +
-

입력

+

+ 입력 +

{sample.inputData}
-

출력

+

+ 출력 +

{sample.expectedOutput}
@@ -374,12 +423,20 @@ export default function CodingTest() {
- +
-

문제가 선택되지 않았습니다.

-

우측 상단의 "문제 생성" 버튼을 눌러 시작하세요.

+

+ 문제가 선택되지 않았습니다. +

+

+ 우측 상단의 "문제 생성" 버튼을 눌러 시작하세요. +

)} @@ -388,7 +445,6 @@ export default function CodingTest() { {/* 오른쪽: 코드 에디터 및 실행 결과 */}
- {/* 1. 코드 에디터 카드 */}
{/* 에디터 툴바 */} @@ -400,11 +456,19 @@ export default function CodingTest() { onClick={() => handleChangeLanguage(opt.value)} className={`px-3 py-1 text-[11px] font-medium rounded-md flex items-center gap-1.5 transition-all duration-200 ${ language === opt.value - ? `bg-[#1e293b] text-white shadow-lg border ${opt.activeBorder || 'border-white/10'}` + ? `bg-[#1e293b] text-white shadow-lg border ${ + opt.activeBorder || "border-white/10" + }` : "text-slate-400 hover:text-slate-200 hover:bg-white/5" }`} > -
+
{opt.label} ))} @@ -427,9 +491,9 @@ export default function CodingTest() {
{/* 에디터 영역 */} -
+