Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_API_BASE_URL=http://localhost:8080
18 changes: 13 additions & 5 deletions src/features/auth/GithubCallback.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,21 @@ export default function GithubCallback() {

useEffect(() => {
const params = new URLSearchParams(location.search);
const token = params.get("token"); // ?token=... 이라고 온다고 가정

if (token) {
localStorage.setItem("accessToken", token);
navigate("/", { replace: true });
const accessToken = params.get("accessToken");
const refreshToken = params.get("refreshToken");
const email = params.get("email");
const username = params.get("username");

if (accessToken && refreshToken) {
localStorage.setItem("accessToken", accessToken);
localStorage.setItem("refreshToken", refreshToken);
if (email) localStorage.setItem("userEmail", email);
if (username) localStorage.setItem("username", username);

navigate("/", { replace: true }); // 로그인 후 홈으로
} else {
navigate("/login", { replace: true });
navigate("/login", { replace: true }); // 실패 시 로그인 페이지로
}
}, [location, navigate]);

Expand Down
41 changes: 32 additions & 9 deletions src/features/home/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,47 @@ export default function Home() {

if (!init) return null;

const username = localStorage.getItem("username");
const hasToken = !!localStorage.getItem("accessToken");

return (
<div className="min-h-screen flex flex-col relative overflow-hidden text-white">

{/* --- 상단 헤더 (로그인 / 사용자명 / 로그아웃) --- */}
<header className="w-full flex justify-end p-4 absolute z-20">
<Link
to="/login"
className="text-sm px-4 py-2 rounded-full border border-white/20 bg-black/30 hover:bg-white/10 transition"
>
Login
</Link>
{hasToken ? (
<div className="flex items-center gap-3">
<span className="text-sm px-4 py-2 rounded-full border border-white/20 bg-black/30">
{username || "User"}
</span>
<button
onClick={() => {
localStorage.clear();
window.location.reload();
}}
className="text-sm px-3 py-2 rounded-full border border-white/20 bg-black/30 hover:bg-white/10 transition"
>
Logout
</button>
</div>
) : (
<Link
to="/login"
className="text-sm px-4 py-2 rounded-full border border-white/20 bg-black/30 hover:bg-white/10 transition"
>
Login
</Link>
)}
</header>

<Particles
id="tsparticles"
options={particlesOptions}
key={particlesVersion}
className="absolute inset-0 z-0 pointer-events-none"
/>

{/* --- 헤더 --- */}
{/* --- 브랜드 타이틀 --- */}
<header className="flex flex-col items-center justify-center pt-[18vh] text-center z-10 relative">
<h1
className={`font-extrabold tracking-tight select-none text-transparent bg-clip-text
Expand All @@ -45,15 +68,15 @@ export default function Home() {
</h1>
</header>

{/* --- 메인 콘텐츠 --- */}
{/* --- 메인 메뉴 (3개 버튼) --- */}
<main className="flex-1 flex items-start justify-center z-10 mt-[120px] md:mt-[140px] px-6">
<div className="w-full max-w-[1600px] mx-auto">
<div
className={`flex items-center justify-center whitespace-nowrap
gap-x-[60px] md:gap-x-[90px] lg:gap-x-[110px]`}
>
{items.map(({ title, href, icon }) => {
const Icon = icon; // 🔥 ESLint가 확실히 인식하게 명시적으로 지정
const Icon = icon;

return (
<Link
Expand Down
44 changes: 35 additions & 9 deletions src/features/review/CodeReview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
IconCopy,
} from "@tabler/icons-react";
import Particles from "@tsparticles/react";
import { fetchCodeReview } from "../../api/reviewService";

// 👇 필요 없으니까 삭제: import { fetchCodeReview } from "../../api/reviewService";

const particlesOptions = {
background: { color: { value: "transparent" } },
Expand All @@ -25,6 +26,8 @@ const particlesOptions = {
},
};

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "";

function formatReviewText(review) {
if (!review) return "";
return review
Expand Down Expand Up @@ -55,7 +58,6 @@ export default function Review() {
if (nextMode === mode) return;
setMode(nextMode);

// 입력 영역 초기화
setCode("");
setRepoUrl("");
setUserComment("");
Expand Down Expand Up @@ -87,11 +89,33 @@ export default function Review() {
setShowQuestions(false);

try {
const data = await fetchCodeReview(
code,
userComment,
mode === "repo" ? repoUrl : null
);
const accessToken = localStorage.getItem("accessToken");

const response = await fetch(`${API_BASE_URL}/api/review`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
},
body: JSON.stringify({
code,
comment: userComment,
repoUrl: mode === "repo" ? repoUrl : null,
}),
});

if (response.status === 401 || response.status === 403) {
throw new Error("로그인이 필요합니다. GitHub 로그인 후 다시 시도해 주세요.");
}

if (!response.ok) {
const text = await response.text().catch(() => "");
throw new Error(
`코드 리뷰 요청 실패 (status: ${response.status}) ${text || ""}`.trim()
);
}

const data = await response.json();

const reviewText =
typeof data?.review === "string"
Expand All @@ -102,12 +126,14 @@ export default function Review() {
? data
: "";

if (!reviewText) throw new Error("AI 응답이 비어 있습니다.");
if (!reviewText) {
throw new Error("AI 응답이 비어 있습니다.");
}

setReview(reviewText);
setQuestions(Array.isArray(data.questions) ? data.questions : []);
} catch (err) {
setError(err.message);
setError(err.message || "코드 리뷰 요청 중 오류가 발생했습니다.");
} finally {
setIsLoading(false);
}
Expand Down