Skip to content

Commit

Permalink
Merge branch 'develop' into feature/resume
Browse files Browse the repository at this point in the history
  • Loading branch information
Gaoridang authored May 11, 2024
2 parents 0f0430e + c34c4bf commit c5fdaeb
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/api/authApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const getNewAccessToken = async (accessToken: string, refreshToken: strin
headers: { Authorization: `Bearer ${accessToken}` },
}
);

Check failure on line 55 in src/api/authApi.ts

View workflow job for this annotation

GitHub Actions / Run Lint

Delete `··`
return response.data;
};

Expand Down
76 changes: 55 additions & 21 deletions src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
getTokenFromLocalStorage,
setTokenToLocalStorage,
} from '@/components/signIn/utils/getToken';
import axios from 'axios';
import axios, { AxiosError } from 'axios';
import { getNewAccessToken } from './authApi';
import toast from 'react-hot-toast';

Expand All @@ -26,43 +26,77 @@ instance.interceptors.request.use((config) => {
return config;
});

interface FailedRequest {
resolve: (token: string | null) => void;
reject: (reason?: any) => void;
}

let isRefreshing = false;
let failedQueue: FailedRequest[] = [];

const processQueue = (error: AxiosError | null, token: string | null = null) => {
failedQueue.forEach((prom) => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});

failedQueue = [];
};

instance.interceptors.response.use(
async (response) => {
return response;
},
async (error) => {
const {
config,
response: { status, data },
response: { status },
} = error;

if (status === 403 || status === 401) {
const originalRequest = config;
const { accessToken, refreshToken } = getTokenFromLocalStorage();
if (!isRefreshing) {
isRefreshing = true;
const { accessToken, refreshToken } = getTokenFromLocalStorage();

if (!accessToken || !refreshToken) return;

if (accessToken && refreshToken) {
try {
const { data, code } = await getNewAccessToken(accessToken, refreshToken);
if (code === 200) {
const { accessToken: newAccessToken, refreshToken: newRefreshToken } = data.token;
setTokenToLocalStorage(newAccessToken, newRefreshToken);
axios.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
originalRequest.headers.authorization = `Bearer ${newAccessToken}`;
const { data } = await getNewAccessToken(accessToken, refreshToken);

const { accessToken: newAccessToken, refreshToken: newRefreshToken } = data.token;
setTokenToLocalStorage(newAccessToken, newRefreshToken);
axios.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
originalRequest.headers.authorization = `Bearer ${newAccessToken}`;

return axios(originalRequest);
processQueue(null, newAccessToken);
return axios(originalRequest);
} catch (refreshError) {
if (refreshError instanceof AxiosError) {
processQueue(refreshError, null);
toast.error('접근 권한이 없습니다.');
}
} catch (error) {
// TODO: 토스트 한 번만 뜨게하기, 리다이렉트 효과적으로 처리할 방법
toast.error('접근 권한이 없습니다.');
} finally {
isRefreshing = false;
}
}
// 리프레시 토큰이 없거나 만료되었을 때
if (data.path.startsWith('/api/v2/articles')) {
toast.error('접근 권한이 없습니다.');
return Promise.reject(error);
}
toast.error('다시 로그인 해주세요.');
return Promise.reject(error);

return new Promise((resolve, reject) => {
failedQueue.push({
resolve: () => {
originalRequest.headers.authorization = `Bearer ${
getTokenFromLocalStorage().accessToken
}`;
resolve(axios(originalRequest));
},
reject: () => {
reject(error);
},
});
});
}
return Promise.reject(error);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
.childCommentWrapper {
padding-left: 1rem;
}

.loadingSpinner {
width: 1rem;
height: 1rem;
border: 2px solid #c1c1c1;
border-top: 2px solid transparent;
border-radius: 50%;
margin-left: 1rem;

animation: spin 1s linear infinite;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
6 changes: 5 additions & 1 deletion src/pages/articleDetail/components/comment/ChildComments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ interface ChildCommentsProps {
}

const ChildComments = ({ parentCommentId }: ChildCommentsProps) => {
const { data } = useChildComments(parentCommentId);
const { data, isLoading } = useChildComments(parentCommentId);

const childComments = data?.pages.flatMap((page) => page.comments);

if (isLoading) {
return <div className={styles.loadingSpinner} />;
}

return (
<div className={styles.childCommentWrapper}>
{childComments?.map((comment) => <p key={comment.commentId}>{comment.content}</p>)}
Expand Down

0 comments on commit c5fdaeb

Please sign in to comment.