diff --git a/BE/src/auth/guard/auth.jwt-guard.ts b/BE/src/auth/guard/auth.jwt-guard.ts index 620c074..e5f57ee 100644 --- a/BE/src/auth/guard/auth.jwt-guard.ts +++ b/BE/src/auth/guard/auth.jwt-guard.ts @@ -32,6 +32,7 @@ export class JwtAuthGuard extends NestAuthGuard("jwt") { } else if (err.message === "refresh expired") { throw new ForbiddenException("리프레쉬 토큰이 만료되었습니다."); } + throw new ForbiddenException("유효하지 않은 리프레쉬 토큰입니다."); } return user; diff --git a/BE/src/diaries/diaries.service.ts b/BE/src/diaries/diaries.service.ts index 6907ff0..874b377 100644 --- a/BE/src/diaries/diaries.service.ts +++ b/BE/src/diaries/diaries.service.ts @@ -33,7 +33,6 @@ export class DiariesService { const tagEntities = await this.getTags(tags); const sentimentResult: SentimentDto = await this.getSentiment(content); - console.log(sentimentResult); const diary = await this.diariesRepository.createDiary( createDiaryDto, encryptedContent, diff --git a/FE/.eslintrc.js b/FE/.eslintrc.js index 9851afb..35275b0 100644 --- a/FE/.eslintrc.js +++ b/FE/.eslintrc.js @@ -28,6 +28,8 @@ module.exports = { "react/prop-types": "off", "import/no-extraneous-dependencies": ["off"], "react/no-danger": "off", + "no-alert": "off", + "no-shadow": "off", }, settings: { "import/resolver": { diff --git a/FE/src/components/DiaryModal/DiaryCreateModal.js b/FE/src/components/DiaryModal/DiaryCreateModal.js index 49c9765..6f9de52 100644 --- a/FE/src/components/DiaryModal/DiaryCreateModal.js +++ b/FE/src/components/DiaryModal/DiaryCreateModal.js @@ -8,6 +8,7 @@ import shapeAtom from "../../atoms/shapeAtom"; import ModalWrapper from "../../styles/Modal/ModalWrapper"; import DiaryModalHeader from "../../styles/Modal/DiaryModalHeader"; import deleteIcon from "../../assets/deleteIcon.svg"; +import preventBeforeUnload from "../../utils/utils"; function DiaryCreateModal(props) { const { refetch } = props; @@ -27,15 +28,10 @@ function DiaryCreateModal(props) { }); useEffect(() => { - const handleBeforeUnload = (e) => { - e.preventDefault(); - e.returnValue = ""; - }; - - window.addEventListener("beforeunload", handleBeforeUnload); + window.addEventListener("beforeunload", preventBeforeUnload); return () => { - window.removeEventListener("beforeunload", handleBeforeUnload); + window.removeEventListener("beforeunload", preventBeforeUnload); }; }, []); @@ -69,7 +65,18 @@ function DiaryCreateModal(props) { }, body: JSON.stringify(data.diaryData), }) - .then((res) => res.json()) + .then((res) => { + if (res.status === 200) { + return res.json(); + } + if (res.status === 403) { + alert("로그인이 만료되었습니다. 다시 로그인해주세요."); + localStorage.removeItem("accessToken"); + sessionStorage.removeItem("accessToken"); + window.location.href = "/"; + } + throw new Error("일기 작성에 실패했습니다."); + }) .then(() => { refetch(); setDiaryState((prev) => ({ diff --git a/FE/src/components/DiaryModal/DiaryDeleteModal.js b/FE/src/components/DiaryModal/DiaryDeleteModal.js index d5cac6e..ce3bcf2 100644 --- a/FE/src/components/DiaryModal/DiaryDeleteModal.js +++ b/FE/src/components/DiaryModal/DiaryDeleteModal.js @@ -19,9 +19,22 @@ function DiaryDeleteModal(props) { "Content-Type": "application/json", Authorization: `Bearer ${data.accessToken}`, }, - }).then(() => { - refetch(); - }); + }) + .then((res) => { + if (res.status === 200) { + return res; + } + if (res.status === 403) { + alert("로그인이 만료되었습니다. 다시 로그인해주세요."); + localStorage.removeItem("accessToken"); + sessionStorage.removeItem("accessToken"); + window.location.href = "/"; + } + return null; + }) + .then(() => { + refetch(); + }); } const { diff --git a/FE/src/components/DiaryModal/DiaryReadModal.js b/FE/src/components/DiaryModal/DiaryReadModal.js index 494476c..097ec37 100644 --- a/FE/src/components/DiaryModal/DiaryReadModal.js +++ b/FE/src/components/DiaryModal/DiaryReadModal.js @@ -42,25 +42,58 @@ function DiaryModalEmotionIndicator({ emotion }) { ); } -async function getDiary(accessToken, diaryUuid) { +async function getDiary(accessToken, diaryUuid, setUserState) { return fetch(`http://223.130.129.145:3005/diaries/${diaryUuid}`, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, - }).then((res) => res.json()); + }).then((res) => { + if (res.status === 200) { + return res.json(); + } + if (res.status === 403) { + alert("로그인이 만료되었습니다. 다시 로그인해주세요."); + localStorage.removeItem("accessToken"); + sessionStorage.removeItem("accessToken"); + window.location.href = "/"; + } + if (res.status === 401) { + return fetch("http://223.130.129.145:3005/auth/reissue", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + }) + .then((res) => res.json()) + .then((data) => { + if (localStorage.getItem("accessToken")) { + localStorage.setItem("accessToken", data.accessToken); + } + if (sessionStorage.getItem("accessToken")) { + sessionStorage.setItem("accessToken", data.accessToken); + } + setUserState((prev) => ({ + ...prev, + accessToken: data.accessToken, + })); + }); + } + return {}; + }); } function DiaryReadModal(props) { const { refetch } = props; const [diaryState, setDiaryState] = useRecoilState(diaryAtom); - const userState = useRecoilValue(userAtom); + const [userState, setUserState] = useRecoilState(userAtom); const shapeState = useRecoilValue(shapeAtom); const [shapeData, setShapeData] = React.useState(""); const { data, isLoading, isError } = useQuery( - "diary", - () => getDiary(userState.accessToken, diaryState.diaryUuid), + ["diary", userState.accessToken], + () => getDiary(userState.accessToken, diaryState.diaryUuid, setUserState), { onSuccess: (loadedData) => { const foundShapeData = shapeState.find( @@ -143,7 +176,7 @@ function DiaryReadModal(props) { 태그 - {data.tags.map((tag) => ( + {data.tags?.map((tag) => ( {tag} ))} @@ -151,9 +184,9 @@ function DiaryReadModal(props) { diff --git a/FE/src/components/DiaryModal/DiaryUpdateModal.js b/FE/src/components/DiaryModal/DiaryUpdateModal.js index 85d34df..949a449 100644 --- a/FE/src/components/DiaryModal/DiaryUpdateModal.js +++ b/FE/src/components/DiaryModal/DiaryUpdateModal.js @@ -8,6 +8,7 @@ import shapeAtom from "../../atoms/shapeAtom"; import ModalWrapper from "../../styles/Modal/ModalWrapper"; import DiaryModalHeader from "../../styles/Modal/DiaryModalHeader"; import deleteIcon from "../../assets/deleteIcon.svg"; +import preventBeforeUnload from "../../utils/utils"; async function getDiary(accessToken, diaryUuid) { return fetch(`http://223.130.129.145:3005/diaries/${diaryUuid}`, { @@ -16,7 +17,19 @@ async function getDiary(accessToken, diaryUuid) { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, - }).then((res) => res.json()); + }).then((res) => { + if (res.status === 200) { + return res.json(); + } + if (res.status === 403) { + alert("로그인이 만료되었습니다. 다시 로그인해주세요."); + localStorage.removeItem("accessToken"); + sessionStorage.removeItem("accessToken"); + window.removeEventListener("beforeunload", preventBeforeUnload); + window.location.href = "/"; + } + return {}; + }); } // TODO: 일기 데이터 수정 API 연결 @@ -45,24 +58,34 @@ function DiaryUpdateModal(props) { Authorization: `Bearer ${data.accessToken}`, }, body: JSON.stringify(data.diaryData), - }).then(() => { - refetch(); - setDiaryState((prev) => ({ - ...prev, - isLoading: true, - })); - }); + }) + .then((res) => { + if (res.status === 200) { + return res; + } + if (res.status === 403) { + alert("로그인이 만료되었습니다. 다시 로그인해주세요."); + localStorage.removeItem("accessToken"); + sessionStorage.removeItem("accessToken"); + window.removeEventListener("beforeunload", preventBeforeUnload); + window.location.href = "/"; + } + return null; + }) + .then(() => { + refetch(); + setDiaryState((prev) => ({ + ...prev, + isLoading: true, + })); + }); } useEffect(() => { - const handleBeforeUnload = (e) => { - e.preventDefault(); - e.returnValue = ""; - }; - window.addEventListener("beforeunload", handleBeforeUnload); + window.addEventListener("beforeunload", preventBeforeUnload); return () => { - window.removeEventListener("beforeunload", handleBeforeUnload); + window.removeEventListener("beforeunload", preventBeforeUnload); }; }, []); @@ -166,7 +189,7 @@ function DiaryUpdateModal(props) { } /> - {diaryData.tags.map((tag) => ( + {diaryData.tags?.map((tag) => ( { diff --git a/FE/src/pages/MainPage.js b/FE/src/pages/MainPage.js index ea8c740..70cb826 100644 --- a/FE/src/pages/MainPage.js +++ b/FE/src/pages/MainPage.js @@ -3,7 +3,7 @@ import React, { useEffect } from "react"; import { useQuery } from "react-query"; import styled from "styled-components"; -import { useRecoilState, useSetRecoilState, useRecoilValue } from "recoil"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import diaryAtom from "../atoms/diaryAtom"; import shapeAtom from "../atoms/shapeAtom"; import userAtom from "../atoms/userAtom"; @@ -13,26 +13,65 @@ import DiaryListModal from "../components/DiaryModal/DiaryListModal"; import DiaryUpdateModal from "../components/DiaryModal/DiaryUpdateModal"; import DiaryLoadingModal from "../components/DiaryModal/DiaryLoadingModal"; import StarPage from "./StarPage"; +import preventBeforeUnload from "../utils/utils"; function MainPage() { const [diaryState, setDiaryState] = useRecoilState(diaryAtom); - const userState = useRecoilValue(userAtom); - const [shapeState, setShapeState] = useRecoilState(shapeAtom); + const [userState, setUserState] = useRecoilState(userAtom); + const setShapeState = useSetRecoilState(shapeAtom); + const [loaded, setLoaded] = React.useState(false); const { refetch } = useQuery( - "diaryList", - () => - fetch("http://223.130.129.145:3005/diaries", { + ["diaryList", userState.accessToken], + () => { + return fetch("http://223.130.129.145:3005/diaries", { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${userState.accessToken}`, }, - }).then((res) => res.json()), + }).then((res) => { + if (res.status === 200) { + setLoaded(true); + return res.json(); + } + if (res.status === 403) { + alert("로그인이 만료되었습니다. 다시 로그인해주세요."); + + localStorage.removeItem("accessToken"); + sessionStorage.removeItem("accessToken"); + window.removeEventListener("beforeunload", preventBeforeUnload); + } + if (res.status === 401) { + return fetch("http://223.130.129.145:3005/auth/reissue", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${userState.accessToken}`, + }, + }) + .then((res) => res.json()) + .then((data) => { + if (localStorage.getItem("accessToken")) { + localStorage.setItem("accessToken", data.accessToken); + } + if (sessionStorage.getItem("accessToken")) { + sessionStorage.setItem("accessToken", data.accessToken); + } + setUserState((prev) => ({ + ...prev, + accessToken: data.accessToken, + })); + }); + } + return {}; + }); + }, { onSuccess: (data) => { setDiaryState((prev) => ({ ...prev, diaryList: data })); }, + refetchOnWindowFocus: false, }, ); @@ -60,42 +99,45 @@ function MainPage() { .then((res) => res.json()) .then(async (data) => { setShapeState(() => { - let shapeList = []; - for (let key in data) { - shapeList.push({ - uuid: data[key].uuid, - data: data[key].svg.replace(/<\?xml.*?\?>/, ""), - }); - } + const shapeList = Object.keys(data).map((key) => ({ + uuid: data[key].uuid, + data: data[key].svg.replace(/<\?xml.*?\?>/, ""), + })); return shapeList; }); }); } - getShapeFn(); - }, []); + if (loaded) { + getShapeFn(); + } + }, [loaded]); return ( - <> - { - e.preventDefault(); - setDiaryState((prev) => ({ - ...prev, - isCreate: true, - isRead: false, - isUpdate: false, - isList: false, - })); - }} - /> - - {diaryState.isCreate ? : null} - {diaryState.isRead ? : null} - {diaryState.isUpdate ? : null} - {diaryState.isList ? : null} - {diaryState.isLoading ? : null} - +
+ {loaded ? ( + <> + { + e.preventDefault(); + setDiaryState((prev) => ({ + ...prev, + isCreate: true, + isRead: false, + isUpdate: false, + isList: false, + })); + }} + /> + + {diaryState.isCreate ? : null} + {diaryState.isRead ? : null} + {diaryState.isUpdate ? : null} + {diaryState.isList ? : null} + {diaryState.isLoading ? : null} + + ) : null} +
); } diff --git a/FE/src/utils/utils.js b/FE/src/utils/utils.js index e69de29..a4dd834 100644 --- a/FE/src/utils/utils.js +++ b/FE/src/utils/utils.js @@ -0,0 +1,6 @@ +const preventBeforeUnload = (e) => { + e.preventDefault(); + e.returnValue = ""; +}; + +export default preventBeforeUnload;