diff --git a/src/axios/TokenInterceptor.js b/src/axios/TokenInterceptor.js index b5f28d1..3bfee88 100644 --- a/src/axios/TokenInterceptor.js +++ b/src/axios/TokenInterceptor.js @@ -70,12 +70,16 @@ instance.interceptors.response.use(async function (response) { }); -const Logout = async () => { +export const Logout = async () => { try { - // 로그아웃 API 호출 - await axios.post(`${LOCAL_SPRING_API_URL}/logout`); + const token = localStorage.getItem('accessToken'); + await axios.post(`${LOCAL_SPRING_API_URL}/logout`, null, { + headers: { + Authorization: `Bearer ${token}` + } + }); localStorage.removeItem('accessToken'); - window.location.href = '/'; // 로그인 페이지 이동 + window.location.href = '/'; // 로그인 페이지로 이동 } catch (error) { console.error('로그아웃 오류 발생:', error); } diff --git a/src/components/oauth/KakaoRedirectPage.js b/src/components/oauth/KakaoRedirectPage.js index c2cbb35..dbd76c8 100644 --- a/src/components/oauth/KakaoRedirectPage.js +++ b/src/components/oauth/KakaoRedirectPage.js @@ -1,60 +1,51 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useRef } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -import instance from "../../axios/TokenInterceptor"; // instance를 계속 사용하려면 +import instance from "../../axios/TokenInterceptor"; import { LOCAL_SPRING_API_URL } from "../../constants/api"; -// 카카오 OAuth 인증 후 리디렉션 처리 페이지 const KakaoRedirectPage = () => { - const location = useLocation(); // 현재 URL 정보 - const navigate = useNavigate(); // 페이지 이동을 위한 navigate 함수 + const location = useLocation(); + const navigate = useNavigate(); + const isCalled = useRef(false); // 한 번만 실행되도록 플래그 useEffect(() => { - // 카카오 로그인 인증 코드 처리 함수 const handleOAuthKakao = async (code) => { try { - // 카카오 인증 코드로 서버에 로그인 요청 (여기서 instance 사용) const response = await instance.get( - `${ LOCAL_SPRING_API_URL}/oauth/login/kakao?code=${code}` + `${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); // 토큰을 로컬스토리지에 저장 + 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"); // 사용자 메인 페이지로 이동 + if (role === "ROLE_FIRST") { + navigate("/voice-training"); + } else { + navigate("/keywords"); } } 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' 파라미터 추출 + const code = searchParams.get("code"); - // 인증 코드가 존재하면 로그인 처리 - if (code) { + if (code && !isCalled.current) { + isCalled.current = true; // ✅ 한 번만 호출되도록 설정 handleOAuthKakao(code); } - }, [location, navigate]); // 의존성 배열에 'location'과 'navigate' 추가 + }, [location, navigate]); - return ( -
-
- ); + return
; }; export default KakaoRedirectPage; diff --git a/src/pages/FinalPage.js b/src/pages/FinalPage.js index 57483c3..a8975c6 100644 --- a/src/pages/FinalPage.js +++ b/src/pages/FinalPage.js @@ -1,6 +1,8 @@ +// FinalPage.jsx import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import Logo from "../components/Logo"; +import { Logout } from "../axios/TokenInterceptor"; // 실제 axios 파일 경로에 맞게 수정 const FinalPage = () => { const navigate = useNavigate(); @@ -10,9 +12,9 @@ const FinalPage = () => { setShowConfirm(true); }; - const handleConfirmYes = () => { + const handleConfirmYes = async () => { setShowConfirm(false); - navigate('/'); + await Logout(); // 로그아웃 처리 및 이동 }; const handleConfirmNo = () => { @@ -41,6 +43,7 @@ const FinalPage = () => { ); }; + const styles = { container: { backgroundColor: '#F4E6CE', diff --git a/src/pages/KeywordSelectionPage.js b/src/pages/KeywordSelectionPage.js index 251fbd9..4ce71cc 100644 --- a/src/pages/KeywordSelectionPage.js +++ b/src/pages/KeywordSelectionPage.js @@ -1,12 +1,12 @@ -import React, { useState } from "react"; -import { useNavigate } from "react-router-dom"; +import React, { useState, useEffect } from "react"; +import { useNavigate, useParams } 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"; // 토큰 불러오기 +import { LOCAL_SPRING_API_URL } from "../constants/api"; +import { getAccessToken } from "../components/Header"; const keywords = { "수면 여부 확인": ["아침", "밤"], @@ -16,29 +16,34 @@ const keywords = { "심리적 상태 체크": ["O", "X"] }; -const timeSettingKeywords = { - "수면 여부 확인": ["아침"], - "식사 여부 확인": ["아침", "점심", "저녁"], - "약 복용 여부 확인": ["아침", "점심", "저녁"] -}; +const timeSettingKeywords = new Set([ + "수면 여부 확인", "식사 여부 확인", "약 복용 여부 확인", "활동 여부 확인" +]); const weekdays = ["월", "화", "수", "목", "금", "토", "일"]; +const dayMap = { + "월": "MONDAY", "화": "TUESDAY", "수": "WEDNESDAY", + "목": "THURSDAY", "금": "FRIDAY", "토": "SATURDAY", "일": "SUNDAY" +}; +const reverseDayMap = Object.fromEntries( + Object.entries(dayMap).map(([k, v]) => [v, k]) +); const TimeSetting = ({ value, onSave }) => { - const [tempTime, setTempTime] = useState(value.time); - const [tempDays, setTempDays] = useState(value.days || []); + const [time, setTime] = useState(value.time); + const [days, setDays] = useState(value.days || []); const toggleDay = (day) => { - setTempDays((prev) => - prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day] - ); + const newDays = days.includes(day) + ? days.filter(d => d !== day) + : [...days, day]; + setDays(newDays); + onSave({ time, days: newDays }); }; - const handleSave = () => { - onSave({ - time: tempTime, - days: tempDays - }); + const handleTimeChange = (newTime) => { + setTime(newTime); + onSave({ time: newTime, days }); }; return ( @@ -50,7 +55,7 @@ const TimeSetting = ({ value, onSave }) => { onClick={() => toggleDay(day)} style={{ ...styles.dayButton, - backgroundColor: tempDays.includes(day) ? "#DABEC9" : "#EEE" + backgroundColor: days.includes(day) ? "#DABEC9" : "#EEE" }} > {day} @@ -58,36 +63,102 @@ const TimeSetting = ({ value, onSave }) => { ))} - ); }; +const KeywordOption = ({ category, keyword, data, onToggle, onSave }) => ( +
+ + {data.selected && timeSettingKeywords.has(category) && ( + + )} +
+); + const KeywordSelectionPage = () => { const navigate = useNavigate(); + const { id } = useParams(); const [selected, setSelected] = useState( - Object.keys(keywords).reduce((acc, category) => { - acc[category] = {}; - keywords[category].forEach((keyword) => { - acc[category][keyword] = { - selected: false, - time: "08:00", - days: [] - }; - }); - return acc; - }, {}) + Object.fromEntries( + Object.entries(keywords).map(([category, list]) => [ + category, + Object.fromEntries( + list.map((keyword) => [ + keyword, + { selected: false, time: "08:00", days: [] } + ]) + ) + ]) + ) ); + useEffect(() => { + const fetchData = async () => { + const token = getAccessToken(); + if (!token) return; + + try { + const response = await axios.get( + `${LOCAL_SPRING_API_URL}/all-basic-schedules`, + { + headers: { Authorization: `Bearer ${token}` }, + } + ); + console.log("확인하기"); + const schedules = response.data.result; + console.log("JSON 파싱 직전"); + console.log(schedules); + + const updatedSelected = JSON.parse(JSON.stringify(selected)); + + schedules.forEach(item => { + const [categoryPrefix, keyword] = item.scheduleTitle.split("_"); + const category = Object.keys(keywords).find( + c => c.replace(/\s/g, "") === categoryPrefix + ); + if (!category || !keywords[category].includes(keyword)) return; + + const time = item.startTime.slice(0, 5); + const days = item.days.map(d => reverseDayMap[d]).filter(Boolean); + + updatedSelected[category][keyword] = { + selected: true, + time, + days + }; + }); + + setSelected(updatedSelected); + } catch (error) { + console.error("기존 키워드 데이터를 불러오는 중 오류 발생:", error); + } + }; + + fetchData(); + }, [id]); + const toggleSelection = (category, keyword) => { - setSelected((prev) => ({ + setSelected(prev => ({ ...prev, [category]: { ...prev[category], @@ -100,14 +171,11 @@ const KeywordSelectionPage = () => { }; const saveTimeAndDays = (category, keyword, newData) => { - setSelected((prev) => ({ + setSelected(prev => ({ ...prev, [category]: { ...prev[category], - [keyword]: { - ...prev[category][keyword], - ...newData - } + [keyword]: { ...prev[category][keyword], ...newData } } })); }; @@ -118,37 +186,23 @@ const KeywordSelectionPage = () => { 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) { + for (const [category, options] of Object.entries(selected)) { + for (const [keyword, { selected, time, days }] of Object.entries(options)) { + if (selected && days.length > 0) { payload.push({ - scheduleTitle: `${category} - ${keyword}`, - startTime: item.time + ":00", - days: item.days.map(day => dayMap[day]) + scheduleTitle: `${category.replace(/\s/g, "")}_${keyword}`, + startTime: time + ":00", + days: days.map(d => dayMap[d]) }); } } } - + try { const response = await axios.post( - `${ LOCAL_SPRING_API_URL }/basic-schedules`, // 실제 API 주소로 교체 + `${LOCAL_SPRING_API_URL}/basic-schedules`, payload, { headers: { @@ -173,28 +227,14 @@ const KeywordSelectionPage = () => {

• {category}

{options.map((keyword) => ( -
- - {selected[category][keyword].selected && - timeSettingKeywords[category]?.includes(keyword) && ( - saveTimeAndDays(category, keyword, data)} - /> - )} -
+ toggleSelection(category, keyword)} + onSave={(data) => saveTimeAndDays(category, keyword, data)} + /> ))}
@@ -279,14 +319,6 @@ const styles = { border: "1px solid #888", cursor: "pointer", fontSize: "12px", - }, - saveButton: { - fontSize: "12px", - padding: "5px 10px", - borderRadius: "5px", - backgroundColor: "#DABEC9", - border: "none", - cursor: "pointer" } }; diff --git a/src/pages/LoginPage.js b/src/pages/LoginPage.js index 644963c..53d7793 100644 --- a/src/pages/LoginPage.js +++ b/src/pages/LoginPage.js @@ -2,7 +2,7 @@ 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"; +import { LOCAL_SPRING_API_URL } from "../constants/api"; const LoginPage = () => { const navigate = useNavigate();