Skip to content

Commit

Permalink
로그아웃 구현(api 호출 및 refreshToken 만료 에러처리) (#296)
Browse files Browse the repository at this point in the history
* feat: isValidServerError 타입가드 작성 및 인터셉터에 적용

* feat: 로그아웃 구현
- 로그아웃 버튼 클릭 핸들러 수정
- 세션 만료 시 자동 로그아웃 수행
  • Loading branch information
dlwl98 authored Nov 23, 2023
1 parent 7aaa9da commit 7c627ff
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 13 deletions.
17 changes: 8 additions & 9 deletions src/api/interceptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AxiosError, InternalAxiosRequestConfig } from 'axios';

import { useTokenStore } from '@stores/accessToken.store';

import { CommonErrorResponse } from '@type/api/error';
import { isValidServerError } from '@utils/isValidServerError';

import { axiosInstance } from './axiosInstance';
import { postRefreshAccessToken } from './member/postRefreshAccessToken';
Expand All @@ -21,16 +21,15 @@ export const setAuthorization = (config: InternalAxiosRequestConfig) => {
return config;
};

export const handleAuthError = async (
error: AxiosError<CommonErrorResponse>
) => {
const data = error.response?.data;
export const handleAuthError = async (error: AxiosError) => {
if (error.config && isValidServerError(error)) {
if (error.response?.data.code === 'AUT-002') {
const { accessToken } = await postRefreshAccessToken();
useTokenStore.getState().setAccessToken(accessToken);

if (data?.code === 'AUT-002') {
const { accessToken } = await postRefreshAccessToken();
useTokenStore.getState().setAccessToken(accessToken);
if (error.config) {
return axiosInstance(error.config);
}
}

throw error;
};
10 changes: 7 additions & 3 deletions src/pages/AllServicesPage/AllServicesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useNavigate } from 'react-router-dom';

import { axiosInstance } from '@api/axiosInstance';

import { Header } from '@components/Header';
import { Text } from '@components/shared/Text';

Expand Down Expand Up @@ -40,9 +42,11 @@ export const AllServicesPage = () => {

const logout = () => {
if (myId) {
setLoginInfo(null);
setAccessToken(null);
location.href = '/';
axiosInstance.delete('/auth/logout').finally(() => {
setLoginInfo(null);
setAccessToken(null);
location.href = '/';
});
}
};

Expand Down
24 changes: 24 additions & 0 deletions src/pages/AuthErrorPage/AuthErrorPage.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import styled from '@emotion/styled';

import { Flex } from '@components/shared/Flex';
import { Image } from '@components/shared/Image';

export const PageWrapper = styled.div`
${({ theme }) => theme.STYLES.LAYOUT}
min-height: 100dvh;
background-color: ${({ theme }) => theme.PALETTE.GRAY_100};
`;

export const PageContent = styled(Flex)`
height: 100%;
padding: 40px 0 20px 0;
`;

export const LogoImage = styled(Image)`
padding-bottom: 40px;
`;

export const ButtonContainer = styled(Flex)`
width: 100%;
padding: 0 16px;
`;
97 changes: 97 additions & 0 deletions src/pages/AuthErrorPage/AuthErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useCallback, useLayoutEffect } from 'react';
import { useNavigate } from 'react-router-dom';

import { useQueryClient } from '@tanstack/react-query';

import { Header } from '@components/Header';
import { Navbar } from '@components/Navbar';
import { Button } from '@components/shared/Button';
import { Text } from '@components/shared/Text';

import { usePathnameChange } from '@hooks/usePathnameChange';

import { theme } from '@styles/theme';

import { useTokenStore } from '@stores/accessToken.store';
import { useLoginInfoStore } from '@stores/loginInfo.store';

import { FallbackProps } from '@type/FallbackProps';

import { PATH_NAME } from '@consts/pathName';

import { isValidServerError } from '@utils/isValidServerError';

import LOGO_SRC from '@assets/logoSvg.svg';

import {
ButtonContainer,
LogoImage,
PageContent,
PageWrapper,
} from './AuthErrorPage.styles';

const buttonProps = {
width: '100%',
backgroundColor: 'transparent',
fontWeight: 500,
height: '2rem',
textColor: theme.PALETTE.GRAY_400,
borderColor: theme.PALETTE.GRAY_400,
} as const;

export const AuthErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
if (!isValidServerError(error)) {
throw error;
}
const errorCode = error.response?.data.code;
if (errorCode !== 'AUT-003' && errorCode !== 'AUT-006') {
throw error;
}

const setAceessToken = useTokenStore((state) => state.setAccessToken);
const setLoginInfo = useLoginInfoStore((state) => state.setLoginInfo);

const navigate = useNavigate();
const queryClient = useQueryClient();

const reset = useCallback(() => {
resetErrorBoundary();
queryClient.clear();
}, [queryClient, resetErrorBoundary]);

usePathnameChange(reset);

useLayoutEffect(() => {
setAceessToken(null);
setLoginInfo(null);
}, [setAceessToken, setLoginInfo]);

return (
<>
<PageWrapper>
<Header />
<PageContent direction="column" gap={20} align="center" justify="start">
<LogoImage
src={LOGO_SRC}
width="35%"
height="auto"
alt="pickle logo"
/>
<div>
<Text size={40}>세션이</Text>
<Text size={40}>만료되었습니다</Text>
</div>
<ButtonContainer gap={16}>
<Button {...buttonProps} onClick={() => navigate(PATH_NAME.LOGIN)}>
로그인하기
</Button>
<Button {...buttonProps} onClick={() => navigate(PATH_NAME.MAIN)}>
홈으로
</Button>
</ButtonContainer>
</PageContent>
</PageWrapper>
<Navbar />
</>
);
};
1 change: 1 addition & 0 deletions src/pages/AuthErrorPage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AuthErrorPage';
5 changes: 4 additions & 1 deletion src/routes/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ErrorBoundary } from 'react-error-boundary';
import { createBrowserRouter } from 'react-router-dom';

import { AllServicesPage } from '@pages/AllServicesPage';
import { AuthErrorPage } from '@pages/AuthErrorPage';
import { ChatRoomListPage } from '@pages/ChatRoomListPage';
import { ChattingPage } from '@pages/ChattingPage';
import { CreateCrewPage } from '@pages/CreateCrewPage';
Expand Down Expand Up @@ -44,7 +45,9 @@ export const router = createBrowserRouter([
path: '/',
element: (
<ErrorBoundary FallbackComponent={ErrorPage}>
<Layout />
<ErrorBoundary FallbackComponent={AuthErrorPage}>
<Layout />
</ErrorBoundary>
</ErrorBoundary>
),
children: [
Expand Down
15 changes: 15 additions & 0 deletions src/utils/isValidServerError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AxiosError } from 'axios';

import { CommonErrorResponse } from '@type/api/error';

export const isValidServerError = (
error: Error
): error is AxiosError<CommonErrorResponse> => {
if (error instanceof AxiosError) {
const { response } = error;
if (response?.data) {
return 'code' in response.data;
}
}
return false;
};

0 comments on commit 7c627ff

Please sign in to comment.