Skip to content

Commit

Permalink
Merge pull request #202 from INtiful/fix/go/KAN-126-logout-timer
Browse files Browse the repository at this point in the history
fix: logout timer 버그 수정
  • Loading branch information
hakyoung12 authored Oct 17, 2024
2 parents 24ffbb8 + e87cdb5 commit f86f397
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 78 deletions.
6 changes: 6 additions & 0 deletions src/app/(main)/mypage/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ 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,
}: Readonly<{
children: ReactNode;
}>) => {
const userData = await getUserData();

if (!userData) {
redirect('/gatherings');
}

return (
<main className='mx-auto flex h-full max-w-1200 flex-col bg-var-gray-50 px-16 pt-24 md:px-24 md:pt-32 lg:px-100 dark:bg-neutral-900'>
{/* head */}
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/actions/mypage/putProfileData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ 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,
): Promise<UserData | null> => {
const token = await getCookie('token');

if (!token) {
alert('로그인 세션이 만료되었습니다. 다시 로그인 해주세요.');
toast.error('로그인 세션이 만료되었습니다. 다시 로그인 해주세요.');
redirect('/signin');
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/gatherings/joined/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/Gnb/Gnb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const Gnb = ({ user, token }: GnbProps) => {
</nav>
<div className='flex items-center gap-12'>
<ToggleTheme />
{user && <TokenExpirationTimerLayout token={token} variant='gnb' />}
<TokenExpirationTimerLayout token={token} variant='gnb' />
<UserStatus user={user} token={token} />
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion src/app/components/Gnb/UserStatus.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -42,7 +45,7 @@ describe('UserStatus 컴포넌트', () => {

beforeEach(() => {
(useRouter as jest.Mock).mockReturnValue({ push: mockPush });
render(<UserStatus user={mockUser} token='test-token' />);
render(<UserStatus user={mockUser} token={mockToken} />);
});

afterEach(() => {
Expand Down
16 changes: 3 additions & 13 deletions src/app/components/Gnb/UserStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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('로그아웃에 실패했습니다. 다시 시도해 주세요.');
}
Expand Down
4 changes: 1 addition & 3 deletions src/app/components/Modal/ProfileEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import ModalFrame from './ModalFrame';
import ModalHeader from './ModalHeader';

interface ProfileEditModalProps {
user: UserData | null;
onClose: () => void;
onUploadProfileImage?: (e: ChangeEvent<HTMLInputElement>) => void;
onSubmit?: () => void;
Expand All @@ -20,7 +19,6 @@ interface ProfileEditModalProps {
}

const ProfileEditModal = ({
user,
onClose,
onUploadProfileImage,
onSubmit,
Expand All @@ -32,7 +30,7 @@ const ProfileEditModal = ({
setProfileInput(e.target.value);
};

const isValid = profileInput !== user?.companyName && profileInput !== '';
const isValid = profileInput !== '';

return (
<ModalFrame onClose={onClose}>
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/UserProfileLayout/UserProfileLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const UserProfileLayout = ({ user }: MyGatheringListProps) => {
if (!updatedUser) {
toast.error('프로필 업데이트에 실패했습니다.');
}
toast.success('프로필이 변경되었습니다.');

setIsModalOpen(false);
};
Expand Down Expand Up @@ -78,7 +79,6 @@ const UserProfileLayout = ({ user }: MyGatheringListProps) => {

{isModalOpen && (
<ProfileEditModal
user={user}
onClose={toggleModal}
onUploadProfileImage={onChangeProfileImage({
setProfileImage,
Expand Down
85 changes: 28 additions & 57 deletions src/utils/TokenExpirationTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,54 @@ 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<number>(EXPIRY_TIME / 1000); // 남은 시간 초기값
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false); // 로그인 상태 관리
const [timeLeft, setTimeLeft] = useState<number>(0);
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(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]);

// 로그아웃 로직
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('로그아웃에 실패했습니다. 다시 시도해 주세요.');
}
Expand Down

0 comments on commit f86f397

Please sign in to comment.