diff --git a/src/App.jsx b/src/App.jsx index 4c45bcf..927b6e4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -9,20 +9,49 @@ import Mypage from "./pages/MyPage.jsx"; import Statics from "./pages/Statics.jsx"; import Home from "./pages/Home.jsx"; import EditDiary from "./pages/EditDiary.jsx"; +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; function App() { + + const navigate = useNavigate(); +useEffect(() => { + const checkTokenExpiry = () => { + const token = localStorage.getItem("accessToken"); + const expiry = localStorage.getItem("tokenExpiry"); + + if (!token) { + return; + } + + const expiryMs = Number(expiry); + const now = Date.now(); + + if (!expiry || !Number.isFinite(expiryMs) || now > expiryMs) { + alert("세션이 만료되었습니다. 다시 로그인해주세요."); + localStorage.clear(); + navigate("/"); + } + }; + + checkTokenExpiry(); + const interval = setInterval(checkTokenExpiry, 60000); + + return () => clearInterval(interval); +}, [navigate]); + return ( } /> } /> } /> } /> - } /> + } /> } /> } /> } /> } /> - } /> + } /> ); } diff --git a/src/apis/auth.js b/src/apis/auth.js index 4a6d29e..fb0dc50 100644 --- a/src/apis/auth.js +++ b/src/apis/auth.js @@ -20,7 +20,6 @@ export const login = async (userData) => { return response.data; }; -// apis/auth.js export const logout = async (refreshToken) => { const response = await instance.post('/api/v1/auth/logout', { diff --git a/src/apis/chat.js b/src/apis/chat.js new file mode 100644 index 0000000..6eeed13 --- /dev/null +++ b/src/apis/chat.js @@ -0,0 +1,83 @@ +import instance from "./instance"; + +const logApiError = (label, error) => { + const status = error?.response?.status; + const message = error?.response?.data?.message ?? error?.message; + console.error(label, { status, message }); +}; + +// 채팅 생성 +export const createChat = async (chatData) => { + try { + const res = await instance.post(`/api/v1/chats`, chatData); + return res.data; + } catch (error) { + // 상세 에러 로깅 + logApiError("채팅 생성 실패:", error); + throw error; + } +}; + +// 채팅 음성 생성 +export const createChatVoice = async (chatId, voiceData) => { + try { + const res = await instance.post(`/api/v1/chats/${chatId}/voice`, voiceData); + return res.data; + } catch (error) { + logApiError("음성 생성 실패:", error); + throw error; + } +}; + +// 채팅 메시지 전송 +export const createChatMessage = async (chatId, messageData) => { + try { + const res = await instance.post( + `/api/v1/chats/${chatId}/messages`, + messageData, + ); + return res.data; + } catch (error) { + logApiError("메시지 전송 실패:", error); + throw error; + } +}; + +// 채팅 수정 +export const updateChat = async (chatId) => { + try { + const res = await instance.patch(`/api/v1/chats/${chatId}`); + return res.data; + } catch (error) { + logApiError("채팅 수정 실패:", error); + throw error; + } +}; + +// 대화 중 사진 추가 +export const createChatImage = async (chatId, imageData) => { + try { + const res = await instance.post( + `/api/v1/chats/${chatId}/images`, + imageData, + ); + return res.data; + } catch (error) { + logApiError("이미지 생성 실패:", error); + throw error; + } +}; + +// AI 이미지 생성 요청 +export const createChatImageGeneration = async (chatId, generationData) => { + try { + const res = await instance.post( + `/api/v1/chats/${chatId}/images/generations`, + generationData, + ); + return res.data; + } catch (error) { + logApiError("이미지 생성 실패:", error); + throw error; + } +}; diff --git a/src/apis/diaries.js b/src/apis/diaries.js index ed9fa74..497f0ed 100644 --- a/src/apis/diaries.js +++ b/src/apis/diaries.js @@ -1,16 +1,27 @@ -import instance from "./axios"; +import instance from "./instance"; -// 일기 조회 +// 일기 단건 조회 export const getDiaries = async (id) => { try { const res = await instance.get(`/api/v1/diaries/${id}`); - return res.data; + return res.data.data; } catch (error) { console.error("일기 조회 실패:", error); throw error; } }; +// 일기 목록 조회 +export const getDiariesList = async (params) => { + try { + const res = await instance.get(`/api/v1/diaries`, { params }); + return res.data; + } catch (error) { + console.error("일기 목록 조회 실패:", error); + throw error; + } +}; + // 일기 내용 수정 export const updateDiaries = async (id, updateData) => { try { @@ -26,15 +37,11 @@ export const updateDiaries = async (id, updateData) => { }; // 일기 이미지 수정 -export const updateDiariesImg = async (diary_id, imageFile) => { +export const updateDiariesImg = async (diaryId, imageList) => { try { - const formData = new FormData(); - formData.append("file", imageFile); - const res = await instance.patch( - `/api/v1/diaries/${diary_id}/images`, - formData, - { headers: { "Content-Type": "multipart/form-data" } }, + `/api/v1/diaries/${diaryId}/images`, + imageList, ); return res.data; } catch (error) { @@ -44,9 +51,9 @@ export const updateDiariesImg = async (diary_id, imageFile) => { }; // 일기 삭제 -export const deleteDiaries = async (diary_id) => { +export const deleteDiaries = async (diaryId) => { try { - const res = await instance.delete(`/api/v1/diaries/${diary_id}`); + const res = await instance.delete(`/api/v1/diaries/${diaryId}`); return res.data; } catch (error) { console.error("일기 삭제 실패:", error); @@ -54,3 +61,14 @@ export const deleteDiaries = async (diary_id) => { } }; +// 다이어리 추천 조회 +export const getDiaryRecommendation = async () => { + try { + const res = await instance.get(`/api/v1/diaries/recommendation`); + return res.data; + } catch (error) { + console.error("상태 코드:", error.response?.status); + console.error("에러 메시지:", error.response?.data); + throw error; + } +}; diff --git a/src/apis/instance.js b/src/apis/instance.js index 798c0de..b3c7442 100644 --- a/src/apis/instance.js +++ b/src/apis/instance.js @@ -2,7 +2,7 @@ import axios from "axios"; const instance = axios.create({ baseURL: import.meta.env.VITE_BASE_URL, - timeout: 5000, + timeout: 30000, headers: { "Content-Type": "application/json" }, }); @@ -22,13 +22,13 @@ const processQueue = (error, token = null) => { instance.interceptors.request.use( (config) => { - const accessToken = localStorage.getItem("accessToken"); - if (accessToken) { - config.headers.Authorization = `Bearer ${accessToken}`; + const token = localStorage.getItem("accessToken"); + if (token) { + config.headers.Authorization = `Bearer ${token}`; } return config; }, - (error) => Promise.reject(error) + (error) => Promise.reject(error), ); instance.interceptors.response.use( @@ -53,43 +53,64 @@ instance.interceptors.response.use( try { const refreshToken = localStorage.getItem("refreshToken"); + const accessToken = localStorage.getItem("accessToken"); + if (!refreshToken) { + throw Object.assign(new Error("Missing refresh token"), { + response: { status: 401 }, + }); + } + + const refreshHeaders = accessToken + ? { Authorization: `Bearer ${accessToken}` } + : {}; + const res = await axios.post( `${import.meta.env.VITE_BASE_URL}/api/v1/auth/reissue`, - { refreshToken: refreshToken } + { refreshToken }, + { + headers: refreshHeaders, + timeout: instance.defaults.timeout, + }, ); if (res.data.status === "success") { - const { accessToken, refreshToken: newRefreshToken } = res.data.data; + const tokenPayload = res.data.data ?? res.data; + const { accessToken: newAccessToken, refreshToken: newRefreshToken } = + tokenPayload ?? {}; + if (!newAccessToken) { + throw new Error("Token refresh response missing access token"); + } + + localStorage.setItem("accessToken", newAccessToken); + if (newRefreshToken) { + localStorage.setItem("refreshToken", newRefreshToken); + } + localStorage.setItem("tokenExpiry", Date.now() + 60 * 60 * 1000); - localStorage.setItem("accessToken", accessToken); - localStorage.setItem("refreshToken", newRefreshToken); + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + processQueue(null, newAccessToken); - originalRequest.headers.Authorization = `Bearer ${accessToken}`; - processQueue(null, accessToken); - return instance(originalRequest); } else { - const failError = new Error("토큰 리프레시가 실패했어요"); - processQueue(failError, null); - return Promise.reject(failError); + throw new Error("Token refresh failed"); } } catch (refreshError) { processQueue(refreshError, null); - + const status = refreshError.response?.status; if (status === 401 || status === 403) { - console.warn("세션이 만료되었습니다. 다시 로그인해주세요."); + console.warn("세션이 만료되어 로그아웃됩니다."); localStorage.clear(); window.location.href = "/login"; } - return Promise.reject(refreshError); } finally { isRefreshing = false; } } + return Promise.reject(error); - } + }, ); -export default instance; \ No newline at end of file +export default instance; diff --git a/src/apis/mypages.api.js b/src/apis/mypages.api.js new file mode 100644 index 0000000..a26217c --- /dev/null +++ b/src/apis/mypages.api.js @@ -0,0 +1,48 @@ +import instance from "./instance"; + +export const getMyInfo = async () => { + const response = await instance.get("/api/v1/users/me"); + return response.data; +}; + +export const withdrawAccount = async () => { + const response = await instance.delete("/api/v1/auth/me"); + return response.data; +}; + +export const updateProfile = async (formData) => { + const response = await instance.patch('/api/v1/users/me/profile', formData, { + headers: { + "Content-Type": undefined, + }, + }); + return response.data; +}; + +export const changePassword = async (passwordData) => { + const response = await instance.patch('/api/v1/users/me/password', passwordData); + return response.data; +}; + +export const deleteUser = async () => { + const refreshToken = localStorage.getItem("refreshToken"); + const response = await instance.delete('/api/v1/users/me', { + data: { + refreshToken: refreshToken + } + }); + return response.data; +}; + +export const logout = async () => { + const refreshToken = localStorage.getItem("refreshToken"); + const response = await instance.post('/api/v1/auth/logout', { + refreshToken: refreshToken + }); + return response.data; +}; + +export const getRandomProfile = async () => { + const response = await instance.patch('/api/v1/users/me/profile-images/random'); + return response.data; +}; \ No newline at end of file diff --git a/src/apis/photo.api.js b/src/apis/photo.api.js new file mode 100644 index 0000000..f5ea2b1 --- /dev/null +++ b/src/apis/photo.api.js @@ -0,0 +1,15 @@ +import instance from "./instance"; + +export const getMyGallery = async (yearMonth, tag = "") => { + const response = await instance.get('/api/v1/diaries', { + params: { + imageType: 'MANUAL', + hasPhoto: true, + yearMonth: yearMonth, + limit: 32, + tag: tag || null, + sort: 'createdAt,desc' + } + }); + return response.data; +}; \ No newline at end of file diff --git a/src/apis/statics.js b/src/apis/statics.js new file mode 100644 index 0000000..1ac80bd --- /dev/null +++ b/src/apis/statics.js @@ -0,0 +1,7 @@ +import instance from "./instance"; + +export const getStatistics = async (year, month) => { + const yearMonth = `${year}-${String(month).padStart(2, "0")}`; + const response = await instance.get(`/api/v1/users/me/statistics?yearMonth=${yearMonth}`); + return response.data; +}; \ No newline at end of file diff --git a/src/apis/user.js b/src/apis/user.js new file mode 100644 index 0000000..6076d8b --- /dev/null +++ b/src/apis/user.js @@ -0,0 +1,29 @@ +import instance from "./instance"; + +// 사용자 정보 조회 +export const getUserSummary = async () => { + try { + const res = await instance.get(`/api/v1/users/me/summary`); + return res.data; + } catch (error) { + const status = error.response?.status; + const message = error.response?.data?.message ?? error.message; + console.error("사용자 요약 조회 실패", { status, message }); + throw error; + } +}; + +// 캘린더 목록 조회 +export const getCalendars = async (yearMonth) => { + try { + const res = await instance.get(`/api/v1/users/me/calendars`, { + params: { + yearMonth, + }, + }); + return res.data; + } catch (error) { + console.error("상태 코드:", error.response?.status); + throw error; + } +}; diff --git a/src/assets/Angry.svg b/src/assets/Angry.svg new file mode 100644 index 0000000..fe21c99 --- /dev/null +++ b/src/assets/Angry.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/Special.svg b/src/assets/Special.svg new file mode 100644 index 0000000..5dbcb4e --- /dev/null +++ b/src/assets/Special.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/openPw.svg b/src/assets/openPw.svg new file mode 100644 index 0000000..a87de43 --- /dev/null +++ b/src/assets/openPw.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 78780a7..e6ab0c4 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -8,29 +8,33 @@ import { useState, useEffect } from "react"; const Header = () => { const navigate = useNavigate(); const location = useLocation(); - const [isLogin, setIsLogin] = useState(false); + const isLogin = !!localStorage.getItem("accessToken"); + + const [, forceUpdate] = useState(0); useEffect(() => { - const token = localStorage.getItem("accessToken"); - setIsLogin(!!token); - }, [location.pathname]); - - + const handleStorage = () => forceUpdate((n) => n + 1); + window.addEventListener("storage", handleStorage); + return () => window.removeEventListener("storage", handleStorage); + }, []); return ( navigate("/")}> HEAR_FOR_YOU - - - navigate("/home")} isActive={location.pathname === "/home"}>홈 - navigate("/ai/chats")} isActive={location.pathname === "/ai/chats"}>AI일기 - navigate("/photobook")} isActive={location.pathname === "/photobook"}>사진첩 - navigate("/statics")} isActive={location.pathname === "/statics"}>통계 - - - + + {/* 로그인 시에만 표시 */} + {isLogin && ( + + navigate("/home")} isActive={location.pathname === "/home"}>홈 + navigate("/ai/chats")} isActive={location.pathname === "/ai/chats"}>AI일기 + navigate("/photobook")} isActive={location.pathname === "/photobook"}>사진첩 + navigate("/statics")} isActive={location.pathname === "/statics"}>통계 + + )} + + {isLogin ? ( navigate("/mypage")}> @@ -103,10 +107,8 @@ const MenuText = styled.p` `; const HeaderRight = styled.div` - /* 고정 너비보다는 내부 콘텐츠에 맞게 늘어나도록 처리하는 게 깔끔합니다 */ min-width: 81px; height: 35px; - background-color: #ffe39a; border-radius: 50px; display: flex; align-items: center; @@ -114,6 +116,8 @@ const HeaderRight = styled.div` padding: 8px 12px; flex-shrink: 0; cursor: pointer; + + background-color: ${(props) => (props.isLogin ? "transparent" : "#ffe39a")}; `; const Login_complete = styled.div` display: flex; diff --git a/src/pages/Diary.jsx b/src/pages/Diary.jsx index c2b1522..a5d7496 100644 --- a/src/pages/Diary.jsx +++ b/src/pages/Diary.jsx @@ -1,30 +1,92 @@ import styled from "@emotion/styled"; -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { getDiaries, deleteDiaries } from "../apis/diaries"; + import Header from "../components/Header"; +import Sadness from "../assets/Sadness.svg"; +import Special from "../assets/Special.svg"; import Happy from "../assets/Happy.svg"; import ArrowRight from "../assets/ArrowRight.svg"; -import Test from "../assets/Test.svg"; import Star from "../assets/Star.svg"; -import { useNavigate } from "react-router-dom"; + +const EMOTION_MAP = { + HAPPY: { text: "행복한", color: "#5dc19b", icon: Happy }, + SAD: { text: "슬픈", color: "#89D9FF", icon: Sadness }, + ANGRY: { text: "화나는", color: "#FEA2A9", icon: Special }, + ANXIETY: { text: "불안한", color: "#CBA3FF", icon: Special }, + NEUTRAL: { text: "평범한", color: "#FCD671", icon: Star }, +}; const Diary = () => { const navigate = useNavigate(); - // 슬라이드 이미지 배열 (필요시 추가) - const images = [Test, Test, Test]; // 같은 이미지 3개 (실제로는 다른 이미지 사용) + const { id } = useParams(); + + const [diary, setDiary] = useState(null); + const [isLoading, setIsLoading] = useState(true); const [currentIndex, setCurrentIndex] = useState(0); + useEffect(() => { + const fetchDiary = async () => { + try { + setIsLoading(true); + const data = await getDiaries(id); + setDiary(data); + } catch (error) { + console.error("일기를 불러오는 데 실패했습니다.", error); + alert("일기를 불러올 수 없습니다."); + navigate("/home"); + } finally { + setIsLoading(false); + } + }; + + if (id) { + fetchDiary(); + } + }, [id, navigate]); + + const currentEmotion = EMOTION_MAP[diary?.emotion] || EMOTION_MAP.HAPPY; + + const handleDelete = async () => { + const isConfirm = window.confirm("정말 이 일기를 삭제하시겠습니까?"); + if (!isConfirm) return; + + try { + await deleteDiaries(id); + alert("삭제되었습니다."); + navigate("/home"); + } catch (error) { + console.error("일기 삭제 실패:", error); + alert("일기 삭제에 실패했습니다."); + } + }; + const goToPrevious = () => { setCurrentIndex((prevIndex) => - prevIndex === 0 ? images.length - 1 : prevIndex - 1, + prevIndex === 0 ? diary?.imageUrls.length - 1 : prevIndex - 1, ); }; const goToNext = () => { setCurrentIndex((prevIndex) => - prevIndex === images.length - 1 ? 0 : prevIndex + 1, + prevIndex === diary?.imageUrls.length - 1 ? 0 : prevIndex + 1, ); }; + if (isLoading) { + return ( + +
+ 로딩 중... + + ); + } + + if (!diary) { + return null; + } + return (
@@ -32,69 +94,84 @@ const Diary = () => { - - - 행복한 하루에요! + + + {currentEmotion.text} 하루에요! - navigate("/editDiary")}> + navigate(`/editDiary/${id}`)}> 수정하기 수정하기 이동 - navigate("/home")}> - 삭제 - + 삭제 + -

2026년 7월 20일

-

- 오늘은 정말 행복한 하루였어요! 아침에 일어나서 햇살이 너무 - 좋아서 기분이 좋았어요. 친구들과 함께 공원에서 피크닉을 했는데, - 맛있는 음식과 좋은 대화로 즐거운 시간을 보냈어요. 저녁에는 - 가족과 함께 맛있는 저녁을 먹으면서 웃음이 끊이지 않았어요. 오늘 - 하루가 너무 소중하고 행복했어요! -

+

+ {diary.createdAt + ? `${diary.createdAt.split("-")[0]}년 ${parseInt(diary.createdAt.split("-")[1])}월 ${parseInt(diary.createdAt.split("-")[2])}일` + : "날짜 정보 없음"} +

+

{diary.content}

+ - #행복 - #행복 - #행복 - #행복 - #행복 + {diary.tags?.map((tag, idx) => ( + #{tag} + ))}
+ - - - - - - - - - {images.map((_, index) => ( - setCurrentIndex(index)} + {diary.imageUrls && diary.imageUrls.length > 0 ? ( + <> + - ))} - + {diary.imageUrls.length > 1 && ( + <> + + + + + + + + {diary.imageUrls.map((_, index) => ( + setCurrentIndex(index)} + /> + ))} + + + )} + + ) : ( + 이미지가 없습니다 + )}
- - - 2026년 7월은 평범한 하루들이 이어지면서도, 한편으로는 - 불안한 마음이 종종 찾아왔던 달이었네요. 그래도 다음 달에는 조금 더 - 편안하고 행복한 순간들이 많이 찾아오길 바라요. 이번 달에는 총 19일 - 동안 꾸준히 일기를 남겨 주셨어요! 다음 달에는 달력이 더 다양한 - 감정들로 가득 채워지길 기대할게요 ! - - - + + {diary.aiFeedback ? ( + + {diary.aiFeedback} + + + ) : ( + + AI 피드백이 도착하지 않았어요! + + + )}
@@ -138,7 +215,7 @@ const CategoryTitle = styled.div` font-weight: bold; span { - color: #5dc19b; + color: ${(props) => props.color || "#5dc19b"}; } `; @@ -203,7 +280,8 @@ const DiaryImg = styled.div` position: relative; `; -// 슬라이드 컨테이너 +const NoImageText = styled.span``; + const SlideContainer = styled.div` position: relative; width: 300px; @@ -215,7 +293,6 @@ const SlideContainer = styled.div` justify-content: center; `; -// 슬라이드 이미지 const SlideImage = styled.img` width: 100%; height: 100%; @@ -223,7 +300,6 @@ const SlideImage = styled.img` transition: opacity 0.3s ease-in-out; `; -// 이전 버튼 const PrevButton = styled.button` position: absolute; left: 12px; @@ -248,7 +324,6 @@ const PrevButton = styled.button` } `; -// 다음 버튼 const NextButton = styled.button` position: absolute; right: 12px; @@ -273,12 +348,10 @@ const NextButton = styled.button` } `; -// 화살표 아이콘 const Arrow = styled.span` font-weight: bold; `; -// 도트 컨테이너 const DotContainer = styled.div` position: absolute; bottom: 12px; @@ -289,7 +362,6 @@ const DotContainer = styled.div` z-index: 10; `; -// 도트 const Dot = styled.button` width: 8px; height: 8px; @@ -308,11 +380,19 @@ const Dot = styled.button` const AiFeedbackBox = styled.div` display: flex; flex-direction: row; - gap: 38px; + align-items: center; + gap: 20px; + width: 100%; `; const AiFeedback = styled.div` position: relative; + flex: 1; + text-align: center; + padding: 20px; + border-radius: 12px; + border: 2px solid #e5d7b2; + background-color: #fff9eb; span { color: #daa005; font-weight: bold; @@ -323,7 +403,6 @@ const AiFeedback = styled.div` border: 2px solid #e5d7b2; background-color: #fff9eb; - /* 오른쪽 면에 삼각형 말풍선 추가 */ &::after { content: ""; position: absolute; @@ -337,7 +416,6 @@ const AiFeedback = styled.div` border-left: 12px solid #fff9eb; } - /* 삼각형 테두리 (border 표현) */ &::before { content: ""; position: absolute; @@ -358,4 +436,11 @@ const AiFeedbackIcon = styled.img` height: 100px; `; +const LoadingText = styled.div` + font-size: 18px; + color: #828282; + text-align: center; + margin-top: 50px; +`; + export default Diary; diff --git a/src/pages/EditDiary.jsx b/src/pages/EditDiary.jsx index fdb7ad6..312199e 100644 --- a/src/pages/EditDiary.jsx +++ b/src/pages/EditDiary.jsx @@ -1,5 +1,6 @@ import styled from "@emotion/styled"; -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; +import { useNavigate, useParams } from "react-router-dom"; import Header from "../components/Header"; import Happy from "../assets/Happy.svg"; import ArrowRight from "../assets/ArrowRight.svg"; @@ -8,25 +9,76 @@ import NoAngry from "../assets/NoAngry.svg"; import NoStar from "../assets/NoStar.svg"; import NoSad from "../assets/NoSad.svg"; import NoAnxiety from "../assets/NoAnxiety.svg"; -import { useNavigate } from "react-router-dom"; + +import { getDiaries, updateDiaries } from "../apis/diaries"; const EditDiary = () => { const navigate = useNavigate(); - // 슬라이드 이미지 배열 (필요시 추가) - const images = [Test, Test, Test]; // 같은 이미지 3개 (실제로는 다른 이미지 사용) + const { id } = useParams(); + + const [content, setContent] = useState(""); + const [date, setDate] = useState(""); + const [hashTags, setHashTags] = useState([]); + const [imageUrls, setImageUrls] = useState([Test]); const [currentIndex, setCurrentIndex] = useState(0); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchDiaryData = async () => { + if (!id) return; + try { + setIsLoading(true); + const data = await getDiaries(id); + + setDate(data.createdAt || ""); + setContent(data.content || ""); + setHashTags(data.tags || []); + + if (data.imageUrls && data.imageUrls.length > 0) { + setImageUrls(data.imageUrls); + } + } catch (error) { + console.error("데이터를 불러오는데 실패했습니다.", error); + alert("일기 데이터를 가져올 수 없습니다."); + } finally { + setIsLoading(false); + } + }; + + fetchDiaryData(); + }, [id]); const goToPrevious = () => { setCurrentIndex((prevIndex) => - prevIndex === 0 ? images.length - 1 : prevIndex - 1, + prevIndex === 0 ? imageUrls.length - 1 : prevIndex - 1, ); }; const goToNext = () => { setCurrentIndex((prevIndex) => - prevIndex === images.length - 1 ? 0 : prevIndex + 1, + prevIndex === imageUrls.length - 1 ? 0 : prevIndex + 1, ); }; + + const handleUpdate = async () => { + try { + await updateDiaries(id, { content }); + alert("일기가 성공적으로 수정되었습니다."); + navigate(`/diary/${id}`); + } catch (error) { + console.error("일기 수정 실패:", error); + alert( + "일기 수정에 실패했습니다: " + + (error.response?.data?.message || error.message), + ); + } + }; + + if (isLoading) + return ( + 로딩 중... + ); + return (
@@ -41,58 +93,60 @@ const EditDiary = () => { - navigate("/diary")}> + navigate(`/diary/${id}`)}> 취소 - navigate("/diary")}> - 완료 - + 완료 -

2026년 7월 20일

-

- 오늘은 정말 행복한 하루였어요! 아침에 일어나서 햇살이 너무 - 좋아서 기분이 좋았어요. 친구들과 함께 공원에서 피크닉을 했는데, - 맛있는 음식과 좋은 대화로 즐거운 시간을 보냈어요. 저녁에는 - 가족과 함께 맛있는 저녁을 먹으면서 웃음이 끊이지 않았어요. 오늘 - 하루가 너무 소중하고 행복했어요! -

+

+ {date.includes("-") + ? `${date.split("-")[0]}년 ${parseInt(date.split("-")[1])}월 ${parseInt(date.split("-")[2])}일` + : date} +

+