diff --git a/src/api/reviewService.js b/src/api/reviewService.js index ada5e6c..ea2b8ed 100644 --- a/src/api/reviewService.js +++ b/src/api/reviewService.js @@ -1,31 +1,55 @@ -const BASE_URL = "/api"; +// src/api/reviewService.js -/** - * AI 코드 리뷰 요청 - * @param {string} code - * @param {string} [comment] - * @param {string} [repoUrl] - */ -export const fetchCodeReview = async (code, comment, repoUrl) => { - const formData = new FormData(); - formData.append("code", code); +// 중요: 백엔드 주소를 정확하게 입력 (Proxy 설정이 없다면 전체 주소 필수) +const BASE_URL = "http://localhost:8080/api"; - if (comment) formData.append("comment", comment); - if (repoUrl) formData.append("repo_url", repoUrl); +export const fetchCodeReview = async (code, comment) => { + // 1. 데이터 객체 생성 + const payload = { + code: code, + comment: comment && comment.trim() ? comment.trim() : null, + }; - const res = await fetch(`${BASE_URL}/review/`, { - method: "POST", - body: formData, - }); + try { + // 2. fetch 요청 (JSON 모드) + const res = await fetch(`${BASE_URL}/review`, { + method: "POST", + headers: { + "Content-Type": "application/json", // 나 JSON 보낸다고 알려줌 + }, + body: JSON.stringify(payload), // 객체를 문자열로 변환 + }); - if (!res.ok) { - const err = await res.json().catch(() => ({})); - throw new Error( - err.detail || - err.error || - `AI 리뷰 요청 실패: ${res.statusText || res.status}`, - ); - } + // 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; + } catch { + // JSON 파싱 실패 시 raw text 사용 + } + throw new Error(errMsg || `요청 실패 (${res.status})`); + } + + if (!raw) return {}; + + // 정상 응답 파싱 + try { + return JSON.parse(raw); + } catch { + return { review: raw, questions: [] }; + } - return await res.json(); -}; + } catch (error) { + console.error("API 요청 실패:", error); + // "Failed to fetch"는 보통 서버가 꺼져있거나 주소가 틀렸을 때 발생 + if (error.message === "Failed to fetch") { + 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 01740ef..7aafd00 100644 --- a/src/features/codingTest/CodingTest.jsx +++ b/src/features/codingTest/CodingTest.jsx @@ -1,3 +1,4 @@ +// src/features/coding/CodingTest.jsx import { useState } from "react"; import { Link } from "react-router-dom"; import { @@ -123,16 +124,18 @@ export default function CodingTest() { const [result, setResult] = useState(null); const [errorMsg, setErrorMsg] = useState(""); - // AI 피드백을 보여줄지 결과 요약을 보여줄지 토글하는 상태 + // AI 피드백(리뷰/면접질문) 구역을 열지 말지 토글 const [showFeedback, setShowFeedback] = useState(false); + // 코드 리뷰 vs 예상 면접 질문 토글 상태 + const [showInterview, setShowInterview] = useState(false); + // 언어 변경 const handleChangeLanguage = (nextLang) => { if (code !== LANGUAGE_TEMPLATES[language] && code.trim() !== "") { - // window.confirm 대신 커스텀 모달이 권장되지만, 빠른 해결을 위해 일단 유지 - if (!window.confirm("언어를 변경하면 작성 중인 코드가 초기화됩니다. 계속하시겠습니까?")) { - return; - } + if (!window.confirm("언어를 변경하면 작성 중인 코드가 초기화됩니다. 계속하시겠습니까?")) { + return; + } } setLanguage(nextLang); setCode(LANGUAGE_TEMPLATES[nextLang] || ""); @@ -144,6 +147,7 @@ export default function CodingTest() { setErrorMsg(""); setResult(null); setShowFeedback(false); + setShowInterview(false); try { const data = await fetchRandomProblem(difficulty); setProblem(data); @@ -171,6 +175,7 @@ export default function CodingTest() { setErrorMsg(""); setResult(null); setShowFeedback(false); + setShowInterview(false); try { const res = await submitCode({ @@ -180,8 +185,9 @@ export default function CodingTest() { userId: 1, // Long 타입이므로 숫자 1 사용 }); setResult(res); - // AI 피드백이 있다면, 기본적으로 피드백 화면을 보여주도록 설정 - if (res.aiFeedback) { + + // aiFeedback 또는 interviewQuestions가 있으면 피드백 영역 기본 ON + if (res.aiFeedback || (res.interviewQuestions && res.interviewQuestions.length > 0)) { setShowFeedback(true); } else { setShowFeedback(false); @@ -453,7 +459,7 @@ export default function CodingTest() { {/* 상단 요약/피드백 토글 */}