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 */} 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/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: { 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/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('로그아웃에 실패했습니다. 다시 시도해 주세요.'); } 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 && ( { 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); - logout(); } }, [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('로그아웃에 실패했습니다. 다시 시도해 주세요.'); }