From 83fc782586261b7bf9c0b04a262dfe96de928ad5 Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Wed, 16 Oct 2024 10:13:07 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[KAN-126]=20fix:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EB=95=8C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9D=B4=EC=A0=84=20=ED=83=80=EC=9D=B4=EB=A8=B8=20?= =?UTF-8?q?=EB=A0=8C=EB=8D=94=EB=A7=81=EB=90=98=EB=8D=98=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/Gnb/Gnb.tsx | 2 +- src/utils/TokenExpirationTimer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/Gnb/Gnb.tsx b/src/app/components/Gnb/Gnb.tsx index a45b68f7..00d42304 100644 --- a/src/app/components/Gnb/Gnb.tsx +++ b/src/app/components/Gnb/Gnb.tsx @@ -67,7 +67,7 @@ const Gnb = ({ user, token }: GnbProps) => {
- {user && } +
diff --git a/src/utils/TokenExpirationTimer.ts b/src/utils/TokenExpirationTimer.ts index 4301f582..a37273ad 100644 --- a/src/utils/TokenExpirationTimer.ts +++ b/src/utils/TokenExpirationTimer.ts @@ -58,7 +58,7 @@ export const TokenExpirationTimer = (token: string | undefined) => { return cleanupTimer; // 클린업 함수 반환 } else { setIsLoggedIn(false); - logout(); + localStorage.removeItem('timeLeft'); } }, [token]); From 5b5aec253bf3fe56b501d790f297bff87dab75cc Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Wed, 16 Oct 2024 13:05:05 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[KAN-126]=20refactor:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=ED=83=80=EC=9D=B4=EB=A8=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/TokenExpirationTimer.ts | 85 ++++++++++--------------------- 1 file changed, 28 insertions(+), 57 deletions(-) diff --git a/src/utils/TokenExpirationTimer.ts b/src/utils/TokenExpirationTimer.ts index a37273ad..92c7b32c 100644 --- a/src/utils/TokenExpirationTimer.ts +++ b/src/utils/TokenExpirationTimer.ts @@ -2,63 +2,44 @@ import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { postUserLogoutData } from '@/app/api/actions/mypage/postUserLogoutData'; import toast from 'react-hot-toast'; - -import { EXPIRY_TIME } from '@/constants/common'; import { deleteCookie } from '@/app/api/actions/cookie/cookie'; export const TokenExpirationTimer = (token: string | undefined) => { const router = useRouter(); - const [timeLeft, setTimeLeft] = useState(EXPIRY_TIME / 1000); // 남은 시간 초기값 - const [isLoggedIn, setIsLoggedIn] = useState(false); // 로그인 상태 관리 + const [timeLeft, setTimeLeft] = useState(0); + const [isLoggedIn, setIsLoggedIn] = useState(false); - // 로컬 스토리지에서 남은 시간을 불러오는 함수 - const loadRemainingTime = () => { - const storedTimeLeft = localStorage.getItem('timeLeft'); - if (storedTimeLeft) { - setTimeLeft(parseInt(storedTimeLeft, 10)); - } else { - const expirationTime = Date.now() + EXPIRY_TIME; - const remainingTime = Math.max( - 0, - Math.floor((expirationTime - Date.now()) / 1000), - ); - setTimeLeft(remainingTime); // 초기값을 남은 시간으로 설정 - localStorage.setItem('timeLeft', remainingTime.toString()); // 남은 시간 저장 - } - }; - - // 타이머를 설정하는 함수 - const startTimer = () => { - const interval = setInterval(() => { - setTimeLeft((prev) => { - if (prev <= 1) { - clearInterval(interval); - logout(); - localStorage.removeItem('timeLeft'); // 시간 초기화 - return 0; - } - const newTimeLeft = prev - 1; - localStorage.setItem('timeLeft', newTimeLeft.toString()); // 남은 시간 저장 - return newTimeLeft; - }); - }, 1000); - - return () => { - clearInterval(interval); - }; + // JWT 디코드하여 만료 시간을 가져오는 함수 + const getTokenExpirationTime = (token: string) => { + const payload = JSON.parse(atob(token.split('.')[1])); + return payload.exp * 1000; // 만료 시간을 밀리초로 변환 }; useEffect(() => { if (token) { setIsLoggedIn(true); - loadRemainingTime(); // 남은 시간 불러오기 - - const cleanupTimer = startTimer(); + const expirationTime = getTokenExpirationTime(token); // 만료시간 + const currentTime = Date.now(); // 현재시간 + const remainingTime = Math.max(0, expirationTime - currentTime); // 남은 시간 계산 + setTimeLeft(Math.floor(remainingTime / 1000)); // 초 단위로 변환 + + // 타이머 설정 + const interval = setInterval(() => { + setTimeLeft((prev) => { + if (prev <= 1) { + clearInterval(interval); + logout(); + return 0; + } + return prev - 1; + }); + }, 1000); - return cleanupTimer; // 클린업 함수 반환 + return () => { + clearInterval(interval); + }; } else { setIsLoggedIn(false); - localStorage.removeItem('timeLeft'); } }, [token]); @@ -66,19 +47,9 @@ export const TokenExpirationTimer = (token: string | undefined) => { const logout = async () => { const result = await postUserLogoutData(); if (result) { - Promise.resolve() - .then(() => { - toast.success('로그아웃이 완료되었습니다.'); - }) - .then(() => { - localStorage.removeItem('timeLeft'); // 로컬 스토리지에서 시간 삭제 - }) - .then(() => { - deleteCookie('token'); // 쿠키에서 토큰 삭제 - }) - .then(() => { - router.push('/gatherings'); - }); + toast.success('로그아웃이 완료되었습니다.'); + deleteCookie('token'); + router.push('/gatherings'); } else { toast.error('로그아웃에 실패했습니다. 다시 시도해 주세요.'); } From 6fd2669b920e23b0a60fb337c9b16b547a4c4c66 Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Wed, 16 Oct 2024 14:09:30 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[KAN-126]=20feat:=20=EB=82=98=EC=9D=98=20?= =?UTF-8?q?=EB=AA=A8=EC=9E=84=20=EC=B5=9C=EC=8B=A0=EC=88=9C=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EB=A0=AC=20Param=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/gatherings/joined/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/gatherings/joined/route.ts b/src/app/api/gatherings/joined/route.ts index a3d1c536..d17b34d5 100644 --- a/src/app/api/gatherings/joined/route.ts +++ b/src/app/api/gatherings/joined/route.ts @@ -14,7 +14,7 @@ export async function GET(req: Request) { const token = await getCookie('token'); const response = await fetch( - `${process.env.NEXT_PUBLIC_API_BASE_URL}/gatherings/joined?offset=${offset}&limit=${limit}`, + `${process.env.NEXT_PUBLIC_API_BASE_URL}/gatherings/joined?offset=${offset}&limit=${limit}&sortOrder=desc`, { method: 'GET', headers: { From 866070f45a790900fd493a600a3f63fc9737c140 Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Wed, 16 Oct 2024 14:14:11 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[KAN-126]=20feat:=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=86=EC=9D=84=20=EB=95=8C=20?= =?UTF-8?q?gatherings=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/mypage/layout.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/(main)/mypage/layout.tsx b/src/app/(main)/mypage/layout.tsx index 622f9c57..9bf71e6f 100644 --- a/src/app/(main)/mypage/layout.tsx +++ b/src/app/(main)/mypage/layout.tsx @@ -2,6 +2,7 @@ import UserProfileLayout from '@/app/components/UserProfileLayout/UserProfileLay import { ReactNode } from 'react'; import Tab from './_component/Tab'; import { getUserData } from '@/app/api/actions/mypage/getUserData'; +import { redirect } from 'next/navigation'; const Layout = async ({ children, @@ -9,6 +10,11 @@ const Layout = async ({ children: ReactNode; }>) => { const userData = await getUserData(); + + if (!userData) { + redirect('/gatherings'); + } + return (
{/* head */} From a10bd2504fb1ba654d74697b7379b1c36308dcbb Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Wed, 16 Oct 2024 15:09:10 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[KAN-126]=20feat:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=88=98=EC=A0=95=EC=8B=9C=20=EC=82=AC=EC=A7=84,?= =?UTF-8?q?=20=ED=9A=8C=EC=82=AC=EB=AA=85=20=EB=91=98=20=EC=A4=91=20?= =?UTF-8?q?=ED=95=98=EB=82=98=EB=A7=8C=20=EB=B0=94=EB=80=8C=EC=96=B4?= =?UTF-8?q?=EB=8F=84=20=EC=88=98=EC=A0=95=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/actions/mypage/putProfileData.ts | 3 ++- src/app/components/Modal/ProfileEditModal.tsx | 4 +--- src/app/components/UserProfileLayout/UserProfileLayout.tsx | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/app/api/actions/mypage/putProfileData.ts b/src/app/api/actions/mypage/putProfileData.ts index a7be5355..54304246 100644 --- a/src/app/api/actions/mypage/putProfileData.ts +++ b/src/app/api/actions/mypage/putProfileData.ts @@ -4,6 +4,7 @@ import { getCookie } from '../cookie/cookie'; import { UserData } from '@/types/client.type'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; +import toast from 'react-hot-toast'; export const putProfileData = async ( formData: FormData, @@ -11,7 +12,7 @@ export const putProfileData = async ( const token = await getCookie('token'); if (!token) { - alert('로그인 세션이 만료되었습니다. 다시 로그인 해주세요.'); + toast.error('로그인 세션이 만료되었습니다. 다시 로그인 해주세요.'); redirect('/signin'); return null; } diff --git a/src/app/components/Modal/ProfileEditModal.tsx b/src/app/components/Modal/ProfileEditModal.tsx index 5aa1b075..ace1bf1f 100644 --- a/src/app/components/Modal/ProfileEditModal.tsx +++ b/src/app/components/Modal/ProfileEditModal.tsx @@ -10,7 +10,6 @@ import ModalFrame from './ModalFrame'; import ModalHeader from './ModalHeader'; interface ProfileEditModalProps { - user: UserData | null; onClose: () => void; onUploadProfileImage?: (e: ChangeEvent) => void; onSubmit?: () => void; @@ -20,7 +19,6 @@ interface ProfileEditModalProps { } const ProfileEditModal = ({ - user, onClose, onUploadProfileImage, onSubmit, @@ -32,7 +30,7 @@ const ProfileEditModal = ({ setProfileInput(e.target.value); }; - const isValid = profileInput !== user?.companyName && profileInput !== ''; + const isValid = profileInput !== ''; return ( diff --git a/src/app/components/UserProfileLayout/UserProfileLayout.tsx b/src/app/components/UserProfileLayout/UserProfileLayout.tsx index e8f0a1ae..2503721b 100644 --- a/src/app/components/UserProfileLayout/UserProfileLayout.tsx +++ b/src/app/components/UserProfileLayout/UserProfileLayout.tsx @@ -43,6 +43,7 @@ const UserProfileLayout = ({ user }: MyGatheringListProps) => { if (!updatedUser) { toast.error('프로필 업데이트에 실패했습니다.'); } + toast.success('프로필이 변경되었습니다.'); setIsModalOpen(false); }; @@ -78,7 +79,6 @@ const UserProfileLayout = ({ user }: MyGatheringListProps) => { {isModalOpen && ( Date: Wed, 16 Oct 2024 17:10:27 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[KAN-126]=20test:=20UserStatus=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20mockToken=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/Gnb/UserStatus.test.tsx | 5 ++++- src/app/components/Gnb/UserStatus.tsx | 16 +++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/app/components/Gnb/UserStatus.test.tsx b/src/app/components/Gnb/UserStatus.test.tsx index 98e7eba1..88d6dd45 100644 --- a/src/app/components/Gnb/UserStatus.test.tsx +++ b/src/app/components/Gnb/UserStatus.test.tsx @@ -7,6 +7,9 @@ import toast from 'react-hot-toast'; import { UserData } from '@/types/client.type'; import '@testing-library/jest-dom'; +const mockToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZWFtSWQiOiIzLTQiLCJ1c2VySWQiOjcyMSwiaWF0IjoxNzI5MDYyODQ2LCJleHAiOjE3MjkwNjY0NDZ9.w4T2gz1nLLTN52UJBSxuy-LAvZI3zYTKKEQfdpmCngc'; + // 유저데이터 모킹 const mockUser: UserData = { id: 1, @@ -42,7 +45,7 @@ describe('UserStatus 컴포넌트', () => { beforeEach(() => { (useRouter as jest.Mock).mockReturnValue({ push: mockPush }); - render(); + render(); }); afterEach(() => { diff --git a/src/app/components/Gnb/UserStatus.tsx b/src/app/components/Gnb/UserStatus.tsx index 61f0883d..1986965c 100644 --- a/src/app/components/Gnb/UserStatus.tsx +++ b/src/app/components/Gnb/UserStatus.tsx @@ -41,19 +41,9 @@ const UserStatus = ({ user, token }: UserStatusProps) => { const handleLogout = async () => { const result = await postUserLogoutData(); if (result) { - Promise.resolve() - .then(() => { - toast.success('로그아웃이 완료되었습니다.'); - }) - .then(() => { - localStorage.removeItem('timeLeft'); // 로컬 스토리지에서 시간 삭제 - }) - .then(() => { - deleteCookie('token'); // 쿠키에서 토큰 삭제 - }) - .then(() => { - router.push('/gatherings'); - }); + toast.success('로그아웃이 완료되었습니다.'); + deleteCookie('token'); + router.push('/gatherings'); } else { toast.error('로그아웃에 실패했습니다. 다시 시도해 주세요.'); }