Skip to content
Open
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
2 changes: 2 additions & 0 deletions Zero100/임유섭/my-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?

.env
2 changes: 2 additions & 0 deletions Zero100/임유섭/my-app/docs/10week.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
10주차 과제 에러 및 공부중
웹 호스팅+ 환경 변수 설정 진행위한 브랜치
27 changes: 27 additions & 0 deletions Zero100/임유섭/my-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions Zero100/임유섭/my-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"dev": "vite --port 3000",
"build": "vite build",
"preview": "vite preview",
"dev:server": "json-server --watch db.json --port 4000"
},
"dependencies": {
"@tanstack/react-query": "^5.90.10",
"axios": "^1.13.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
Expand Down
53 changes: 28 additions & 25 deletions Zero100/임유섭/my-app/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import { Routes, Route } from "react-router-dom";
import Header from "./components/Header";
import Login from "./pages/Login";
import Register from "./pages/Register";
import PrivateRoute from "./components/PrivateRoute";

function Home() {
return <div className="p-6">로그인된 사용자만 볼 수 있는 홈 화면</div>;
}
// src/App.jsx
import { Routes, Route, Navigate } from "react-router-dom";
import LoginPage from "./pages/LoginPage";
import SignUpPage from "./pages/SignUpPage";
import TodoPage from "./pages/TodoPage";
import KakaoCallbackPage from "./pages/KakaoCallbackPage";
import ProtectedRoute from "./components/ProtectedRoute";

export default function App() {
return (
<div className="max-w-xl mx-auto">
<Header />
<Routes>
<Route
path="/"
element={
<PrivateRoute>
<Home />
</PrivateRoute>
}
/>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Routes>
</div>
<Routes>
<Route path="/" element={<Navigate to="/login" replace />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignUpPage />} />

{/* 카카오 콜백 */}
<Route
path="/oauth/kakao/success"
element={<KakaoCallbackPage />}
/>

<Route
path="/todos"
element={
<ProtectedRoute>
<TodoPage />
</ProtectedRoute>
}
/>
</Routes>
);
}
}
25 changes: 25 additions & 0 deletions Zero100/임유섭/my-app/src/api/authApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import apiClient from "./client";

export const kakaoRedirect = async (code) => {
const res = await apiClient.get("/auth/kakao/redirect", {
params: { code },
});

const result = res.data;
// result 구조:
// {
// code,
// message,
// data: {
// httpStatus,
// responseMessage,
// }
// }

return {
code: result.code,
message: result.message,
httpStatus: result.data?.httpStatus,
responseMessage: result.data?.responseMessage,
};
};
40 changes: 40 additions & 0 deletions Zero100/임유섭/my-app/src/api/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// src/api/client.js
import axios from "axios";

export const BASE_URL = import.meta.env.VITE_API_BASE_URL;

// axios 인스턴스 생성
const apiClient = axios.create({
baseURL: BASE_URL,
withCredentials: true, // 백엔드가 쿠키를 쓴다면 필요
});

// 요청 인터셉터: accessToken 있으면 Authorization 헤더에 자동 첨부
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem("accessToken");

if (token && !config.headers.Authorization) {
config.headers.Authorization = `Bearer ${token}`;
}

return config;
});

// 응답 인터셉터: 401 나오면 토큰 삭제 (로그아웃 상태로 정리)
apiClient.interceptors.response.use(
(response) => response,
(error) => {
const status = error.response?.status;

if (status === 401) {
localStorage.removeItem("accessToken");
localStorage.removeItem("user");
// 여기에서 바로 window.location.href = "/login" 해도 되고,
// 화면 쪽에서 에러를 받아서 처리해도 됨.
}

return Promise.reject(error);
}
);

export default apiClient;
16 changes: 16 additions & 0 deletions Zero100/임유섭/my-app/src/api/todoApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// src/api/todoApi.js
import apiClient from "./client";

// TODO 목록 조회
export const fetchTodos = async () => {
const res = await apiClient.get("/todos"); // 실제 경로 확인
return res.data;
};

// TODO 추가
export const createTodo = async (title) => {
const res = await apiClient.post("/todos", { title }); // 실제 경로 확인
return res.data;
};

// TODO 완료 토글 등 나중에 필요하면 추가
20 changes: 20 additions & 0 deletions Zero100/임유섭/my-app/src/components/KakaoLoginButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// src/components/KakaoLoginButton.jsx
import { getKakaoLoginUrl } from "../api/authApi";

export default function KakaoLoginButton() {
const handleClick = () => {
const url = getKakaoLoginUrl();
// 과제 설명에 있는 window.location.ref 는 오타로 보고 href 사용
window.location.href = url;
};

return (
<button
type="button"
onClick={handleClick}
className="w-full mt-4 h-10 rounded-md border flex items-center justify-center gap-2"
>
<span>카카오 계정으로 로그인</span>
</button>
);
}
15 changes: 15 additions & 0 deletions Zero100/임유섭/my-app/src/components/ProtectedRoute.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// src/components/ProtectedRoute.jsx
import { Navigate } from "react-router-dom";

export default function ProtectedRoute({ children }) {
const token = localStorage.getItem("accessToken");
const user = localStorage.getItem("user");

if (!token || !user) {
// 로그인 안 되어 있으면 로그인 페이지로 이동
return <Navigate to="/login" replace />;
}

// 로그인 되어있으면 원래 페이지 렌더
return children;
}
19 changes: 12 additions & 7 deletions Zero100/임유섭/my-app/src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
// src/main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { AuthProvider } from "./context/AuthContext";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "./App";
import "./index.css";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")).render(
<BrowserRouter>
<AuthProvider>
<App />
</AuthProvider>
</BrowserRouter>
);
<React.StrictMode>
<BrowserRouter>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</BrowserRouter>
</React.StrictMode>
);
61 changes: 61 additions & 0 deletions Zero100/임유섭/my-app/src/pages/KakaoCallbackPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// src/pages/KakaoCallbackPage.jsx

import React, { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";

import Text from "../components/Text";
import { kakaoRedirect } from "../api/authApi";
export default function KakaoCallbackPage() {
const navigate = useNavigate();
const location = useLocation();

const code = new URLSearchParams(location.search).get("code");

const { data, isLoading, isError, error } = useQuery({
queryKey: ["kakaoRedirect", code],
queryFn: () => kakaoRedirect(code),
enabled: !!code, // code 있을 때만 호출
});

useEffect(() => {
if (!data) return;


if (data.code === 200) {
alert(data.responseMessage || "카카오 로그인 완료");
navigate("/todo", { replace: true });
return;
}

if (data.code === 401) {
navigate("/signup?from=kakao", { replace: true });
return;
}

alert(data.responseMessage || data.message || "카카오 로그인에 실패했습니다.");
navigate("/login", { replace: true });
}, [data, navigate]);

if (isLoading) {
return (
<div className="flex items-center justify-center h-screen">
<Text as="p">카카오 로그인 처리 중입니다...</Text>
</div>
);
}

if (isError) {
console.error(error);
return (
<div className="flex items-center justify-center h-screen">
<Text as="p">카카오 로그인 중 오류가 발생했습니다.</Text>
</div>
);
}

return (
<div className="flex items-center justify-center h-screen">
<Text as="p">결과 처리 중입니다...</Text>
</div>
);
}
41 changes: 0 additions & 41 deletions Zero100/임유섭/my-app/src/pages/Login.jsx

This file was deleted.

Loading