Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 로그아웃 타이머 및 로그아웃 관련 버그 수정 #223

Merged
merged 5 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions src/app/components/Gnb/TokenExpirationTimerLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('TokenExpirationTimerLayout 컴포넌트', () => {
timeLeft: 300,
});

render(<TokenExpirationTimerLayout token={mockToken} variant='gnb' />);
render(<TokenExpirationTimerLayout token='' variant='gnb' />);

expect(screen.queryByText(/남은 시간:/)).not.toBeInTheDocument();
});
Expand All @@ -40,14 +40,9 @@ describe('TokenExpirationTimerLayout 컴포넌트', () => {
expect(screen.getByText('남은 시간: 2분 0초')).toBeInTheDocument();
});

// 남은 시간이 0일 경우 아무것도 렌더링하지 않아야 함
it('should render nothing when time left is 0', () => {
(TokenExpirationTimer as jest.Mock).mockReturnValue({
isLoggedIn: true,
timeLeft: 0,
});

render(<TokenExpirationTimerLayout token={mockToken} variant='gnb' />);
// 토큰이 없을 경우 아무것도 렌더링하지 않아야 함
it('should not render anything when there is no token', () => {
render(<TokenExpirationTimerLayout token='' variant='gnb' />);

expect(screen.queryByText(/남은 시간:/)).not.toBeInTheDocument();
});
Expand Down
9 changes: 3 additions & 6 deletions src/app/components/Gnb/TokenExpirationTimerLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { TokenExpirationTimer } from '@/utils/TokenExpirationTimer';
import { useEffect, useState } from 'react';

const TimerStyle = {
gnb: 'hidden text-14 font-semibold text-var-orange-50 md:block md:text-16 w-140',
Expand All @@ -17,13 +18,9 @@ const TokenExpirationTimerLayout = ({
token,
variant,
}: TokenExpirationTimerLayoutProps) => {
const { timeLeft, isLoggedIn } = TokenExpirationTimer(token);
const { timeLeft } = TokenExpirationTimer(token);

if (!isLoggedIn) {
return null;
}

return timeLeft > 0 ? (
return token ? (
<p className={TimerStyle[variant]}>
{variant === 'gnb' ? (
<>
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/Gnb/UserStatus.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { render, screen, fireEvent } from '@testing-library/react';
import UserStatus from './UserStatus';
import { useRouter } from 'next/navigation';
import { deleteCookie } from '@/app/api/actions/cookie/cookie';
Expand Down Expand Up @@ -35,6 +35,7 @@ jest.mock('@/app/api/actions/mypage/postUserLogoutData', () => ({

jest.mock('react-hot-toast', () => ({
success: jest.fn(),
error: jest.fn(),
}));

describe('UserStatus 컴포넌트', () => {
Expand Down Expand Up @@ -94,6 +95,5 @@ describe('UserStatus 컴포넌트', () => {
expect(mockPostUserLogoutData).toHaveBeenCalled();
expect(mockToastSuccess).toHaveBeenCalledWith('로그아웃이 완료되었습니다.');
expect(mockDeleteCookie).toHaveBeenCalledWith('token');
expect(mockPush).toHaveBeenCalledWith('/gatherings');
});
});
4 changes: 1 addition & 3 deletions src/app/components/Gnb/UserStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Profile } from '@/public/images';
import Image from 'next/image';
import Link from 'next/link';
import { useState, useRef, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { UserData } from '@/types/client.type';
import { postUserLogoutData } from '@/app/api/actions/mypage/postUserLogoutData';
import toast from 'react-hot-toast';
Expand All @@ -19,7 +18,6 @@ interface UserStatusProps {
const UserStatus = ({ user, token }: UserStatusProps) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const dropDownRef = useRef<HTMLDivElement>(null);
const router = useRouter();

Comment on lines 20 to 21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P4) 혹시 그럼 router 를 빼면 이동은 어떤 식으로 하게 되는지 궁금합니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

마이페이지에서는 레이아웃의 토큰검사로직에 걸려서 gatherings 페이지로 이동되고
이외의 사이트에서는 페이지 이동없이 gnb바만 바뀝니다!

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
Expand All @@ -43,7 +41,7 @@ const UserStatus = ({ user, token }: UserStatusProps) => {
if (result) {
toast.success('로그아웃이 완료되었습니다.');
deleteCookie('token');
router.push('/gatherings');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) 이게 어떤 면에서 문제가 되었을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleteCookie('token');가 실행되기 전에 종종 router.push('/gatherings');가 먼저 실행되어서 쿠키가 삭제되지 않았던 문제로 생각합니다.
then 체이닝으로 묶기엔 마이페이지의 토큰검사 로직도 있고 다른 버그를 일으킬 것 같아서 뺐습니다.

// router.push('/gatherings');
} else {
toast.error('로그아웃에 실패했습니다. 다시 시도해 주세요.');
}
Expand Down
43 changes: 17 additions & 26 deletions src/utils/TokenExpirationTimer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import { postUserLogoutData } from '@/app/api/actions/mypage/postUserLogoutData';
import toast from 'react-hot-toast';
import { deleteCookie } from '@/app/api/actions/cookie/cookie';

export const TokenExpirationTimer = (token: string | undefined) => {
const router = useRouter();
const [timeLeft, setTimeLeft] = useState<number>(0);
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
const [timeLeft, setTimeLeft] = useState<number>(0); // 재렌더링을 위한 스테이트 저장

// JWT 디코드하여 만료 시간을 가져오는 함수
const getTokenExpirationTime = (token: string) => {
Expand All @@ -17,29 +16,21 @@ export const TokenExpirationTimer = (token: string | undefined) => {

useEffect(() => {
if (token) {
setIsLoggedIn(true);
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 () => {
clearInterval(interval);
const expirationTime = getTokenExpirationTime(token); // 만료 시간
const updateRemainingTime = () => {
const currentTime = Date.now(); // 현재 시간
const remainingTime = Math.max(0, expirationTime - currentTime); // 남은 시간 계산
setTimeLeft(Math.floor(remainingTime / 1000)); // 초 단위로 상태 업데이트

if (remainingTime <= 0) {
logout(); // 시간이 다되면 로그아웃
}
};
Comment on lines +19 to +27
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setInterval을 통해서 만료시간에서 1초씩 빼는 로직에서 만료시간에서 현재시간을 빼는 로직으로 바꿨습니다.

} else {
setIsLoggedIn(false);

updateRemainingTime(); // 초기 업데이트
const interval = setInterval(updateRemainingTime, 1000); // 1초마다 업데이트

return () => clearInterval(interval);
}
}, [token]);

Expand All @@ -55,5 +46,5 @@ export const TokenExpirationTimer = (token: string | undefined) => {
}
};

return { timeLeft, isLoggedIn };
return { timeLeft };
};
Loading