} />
+
+ {/* 카카오 로그인 리다이렉트 경로 추가 */}
+ } />
);
diff --git a/src/axios/TokenInterceptor.js b/src/axios/TokenInterceptor.js
new file mode 100644
index 0000000..b5f28d1
--- /dev/null
+++ b/src/axios/TokenInterceptor.js
@@ -0,0 +1,84 @@
+import axios from 'axios';
+import {LOCAL_SPRING_API_URL} from "../constants/api";
+
+const instance = axios.create();
+
+instance.defaults.withCredentials = true;
+
+// 요청 인터셉터 : API call 하기 전에 실행
+instance.interceptors.request.use(function (config) {
+
+ // 로컬 스토리지에서 accessToken을 가져온다
+ const accessToken = localStorage.getItem('accessToken');
+ console.log(accessToken);
+
+ // accessToken이 있으면 요청 헤더에 추가한다.
+ if (accessToken)
+ config.headers['Authorization'] = `Bearer ${accessToken}`;
+ return config;
+
+}, function (error) {
+ // 요청 오류 처리
+ return Promise.reject(error);
+});
+
+// 응답 인터셉터 : 응답을 받고 then, catch 처리하기 전에 실행
+// 토큰 재인증, 자동 로그아웃 등 처리
+instance.interceptors.response.use(async function (response) {
+
+ // 응답 데이터 있는 작업 수행
+ // 2xx 범위에 있는 상태 코드인 경우
+ return response;
+}, async function (error) {
+
+ // 응답 오류가 있는 작업 수행
+ // 2xx 범위 밖에 있는 상태 코드인 경우
+
+ const {config, response} = error;
+ if (!response) {
+ console.error('Network or server error', error);
+ return Promise.reject(error);
+ }
+
+ // 백엔드단의 JWT Filter에서 걸리는 에러 처리
+ const {status, data} = response;
+ console.log(response);
+ if (status === 401) {
+ if (data.message === "토큰이 없습니다") {
+ await Logout();
+ }
+ if (data.message === "유효하지 않은 토큰") {
+ try {
+ const tokenReissueResult = await instance.post(`${LOCAL_SPRING_API_URL}/reissue`);
+ if (tokenReissueResult.status === 200) {
+ // 재발급 성공시 로컬스토리지에 토큰 저장
+ const accessToken = tokenReissueResult.headers['authorization'] || tokenReissueResult.headers['Authorization'];
+ localStorage.setItem('accessToken', accessToken);
+ // 토큰 재발급 성공, API 재요청
+ console.log("토큰 재발급 성공");
+ return instance(config)
+ } else {
+ await Logout();
+ }
+ } catch (e) {
+ await Logout();
+ }
+ }
+ }
+
+ return Promise.reject(error);
+});
+
+
+const Logout = async () => {
+ try {
+ // 로그아웃 API 호출
+ await axios.post(`${LOCAL_SPRING_API_URL}/logout`);
+ localStorage.removeItem('accessToken');
+ window.location.href = '/'; // 로그인 페이지 이동
+ } catch (error) {
+ console.error('로그아웃 오류 발생:', error);
+ }
+};
+
+export default instance;
\ No newline at end of file
diff --git a/src/components/Header.js b/src/components/Header.js
new file mode 100644
index 0000000..d65a0c0
--- /dev/null
+++ b/src/components/Header.js
@@ -0,0 +1,9 @@
+// components/Header.js
+
+export const getAccessToken = () => {
+ const token = localStorage.getItem("accessToken");
+ if (!token) {
+ console.warn("accessToken이 없습니다.");
+ }
+ return token;
+};
diff --git a/src/components/oauth/KakaoRedirectPage.js b/src/components/oauth/KakaoRedirectPage.js
new file mode 100644
index 0000000..c2cbb35
--- /dev/null
+++ b/src/components/oauth/KakaoRedirectPage.js
@@ -0,0 +1,60 @@
+import React, { useEffect } from "react";
+import { useLocation, useNavigate } from "react-router-dom";
+import instance from "../../axios/TokenInterceptor"; // instance를 계속 사용하려면
+import { LOCAL_SPRING_API_URL } from "../../constants/api";
+
+// 카카오 OAuth 인증 후 리디렉션 처리 페이지
+const KakaoRedirectPage = () => {
+ const location = useLocation(); // 현재 URL 정보
+ const navigate = useNavigate(); // 페이지 이동을 위한 navigate 함수
+
+ useEffect(() => {
+ // 카카오 로그인 인증 코드 처리 함수
+ const handleOAuthKakao = async (code) => {
+ try {
+ // 카카오 인증 코드로 서버에 로그인 요청 (여기서 instance 사용)
+ const response = await instance.get(
+ `${ LOCAL_SPRING_API_URL}/oauth/login/kakao?code=${code}`
+ );
+
+ if (response.data.isSuccess) {
+ // 로그인 성공 시, Authorization 헤더에서 액세스 토큰 가져오기
+ const accessToken = response.headers["Authorization"] || response.headers["authorization"];
+ localStorage.setItem("accessToken", accessToken); // 토큰을 로컬스토리지에 저장
+
+ // 사용자의 역할(role)에 따라 페이지 이동
+ const role = response.data.result;
+ if (role === "GUEST") {
+ navigate("/voice-training"); // 게스트 페이지로 이동
+ } else if (role === "USER") {
+ navigate("/voice-training"); // 사용자 메인 페이지로 이동
+ }
+ } else {
+ // 로그인 실패 처리
+ console.error("OAuth2 로그인 오류");
+ console.log(response.data.code);
+ console.log(response.data.message);
+ }
+ } catch (error) {
+ // 요청 실패 시 처리
+ console.error("로그인 실패", error);
+ }
+ };
+
+ // URL에서 카카오 인증 코드 추출
+ const searchParams = new URLSearchParams(location.search);
+ const code = searchParams.get("code"); // URL에서 'code' 파라미터 추출
+
+ // 인증 코드가 존재하면 로그인 처리
+ if (code) {
+ handleOAuthKakao(code);
+ }
+ }, [location, navigate]); // 의존성 배열에 'location'과 'navigate' 추가
+
+ return (
+
+
+ );
+};
+
+export default KakaoRedirectPage;
diff --git a/src/pages/KeywordSelectionPage.js b/src/pages/KeywordSelectionPage.js
index e0e6d72..251fbd9 100644
--- a/src/pages/KeywordSelectionPage.js
+++ b/src/pages/KeywordSelectionPage.js
@@ -2,8 +2,11 @@ import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import TimePicker from "react-time-picker";
import "react-time-picker/dist/TimePicker.css";
+import { LOCAL_SPRING_API_URL } from "../constants/api";
import "react-clock/dist/Clock.css";
import Logo from "../components/Logo";
+import axios from "axios";
+import { getAccessToken } from "../components/Header"; // 토큰 불러오기
const keywords = {
"수면 여부 확인": ["아침", "밤"],
@@ -109,6 +112,58 @@ const KeywordSelectionPage = () => {
}));
};
+ const handleComplete = async () => {
+ const token = getAccessToken();
+ if (!token) {
+ alert("로그인이 필요합니다.");
+ return;
+ }
+
+ // 요일 변환 맵
+ const dayMap = {
+ "월": "MONDAY",
+ "화": "TUESDAY",
+ "수": "WEDNESDAY",
+ "목": "THURSDAY",
+ "금": "FRIDAY",
+ "토": "SATURDAY",
+ "일": "SUNDAY"
+ };
+
+ // selected 가공
+ const payload = [];
+
+ for (const category in selected) {
+ for (const keyword in selected[category]) {
+ const item = selected[category][keyword];
+ if (item.selected && item.days.length > 0) {
+ payload.push({
+ scheduleTitle: `${category} - ${keyword}`,
+ startTime: item.time + ":00",
+ days: item.days.map(day => dayMap[day])
+ });
+ }
+ }
+ }
+
+ try {
+ const response = await axios.post(
+ `${ LOCAL_SPRING_API_URL }/basic-schedules`, // 실제 API 주소로 교체
+ payload,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ console.log("서버 응답:", response.data);
+ navigate("/final");
+ } catch (error) {
+ console.error("데이터 전송 실패:", error);
+ alert("데이터 전송 중 오류가 발생했습니다.");
+ }
+ };
+
return (
@@ -144,7 +199,7 @@ const KeywordSelectionPage = () => {
))}
-
+
);
};
diff --git a/src/pages/LoginPage.js b/src/pages/LoginPage.js
index b16bba8..644963c 100644
--- a/src/pages/LoginPage.js
+++ b/src/pages/LoginPage.js
@@ -2,12 +2,12 @@ import React from "react";
import { useNavigate } from "react-router-dom";
import Button from "../components/Button";
import Logo from "../components/Logo";
+import { LOCAL_SPRING_API_URL } from "../../constants/api";
const LoginPage = () => {
const navigate = useNavigate();
const handleKakaoLogin = () => {
- //window.location.href = "나중에 카카오 로그인 키. "
- navigate("/signup");
+ window.location.href = `${LOCAL_SPRING_API_URL}/oauth/kakao`;
};
return (