Skip to content

Commit

Permalink
Merge pull request #98 from capstone-maru/feat/error-handler
Browse files Browse the repository at this point in the history
feat: Add Error Handling
  • Loading branch information
cjeongmin authored May 28, 2024
2 parents 488e227 + c9c08ed commit 1651628
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 169 deletions.
1 change: 1 addition & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import React from 'react';
import './globals.scss';
import './lib/axios';

import {
AuthProvider,
Expand Down
71 changes: 71 additions & 0 deletions src/app/lib/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use client';

import axios, { type AxiosError, type AxiosResponse } from 'axios';

import { postTokenRefresh } from '@/features/auth';
import { load, save } from '@/shared/storage';
import { type FailureDTO } from '@/shared/types';

let isRefreshing = false;
let refreshSubscribers: Array<(token: string) => void> = [];

const subscribeTokenRefresh = (callback: (token: string) => void) => {
refreshSubscribers.push(callback);
};

const onRefreshed = (token: string) => {
refreshSubscribers.forEach(callback => {
callback(token);
});
refreshSubscribers = [];
};

axios.interceptors.response.use(
(response: AxiosResponse) => response,
async (error: AxiosError<FailureDTO>) => {
if (error.response?.status !== 401 || error.response?.data.code !== 'C002')
return await Promise.reject(error);

const refreshToken = load<string>({ type: 'local', key: 'refreshToken' });
if (refreshToken == null) {
window.location.href = '/';
return await Promise.reject(error);
}

if (isRefreshing) {
return await new Promise(resolve => {
subscribeTokenRefresh((token: string) => {
const { config } = error;
if (config?.headers != null) {
config.headers.Authorization = `Bearer ${token}`;
resolve(axios(config));
}
});
});
}

isRefreshing = true;
try {
const {
data: { accessToken, refreshToken: newRefreshToken, expiresIn },
} = await postTokenRefresh(refreshToken);

axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
save({ type: 'local', key: 'refreshToken', value: newRefreshToken });
save({ type: 'local', key: 'expiresIn', value: `${expiresIn}` });

const { config } = error;
onRefreshed(accessToken);
if (config != null) {
config.headers.Authorization = `Bearer ${accessToken}`;
return await axios(config);
}
} catch (refreshError) {
return await Promise.reject(error);
} finally {
isRefreshing = false;
}

return await Promise.reject(error);
},
);
1 change: 0 additions & 1 deletion src/app/lib/providers/InitializationProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export function InitialzationProvider({
const { data: userData } = useUserData(auth?.accessToken != null);

useEffect(() => {
console.log(userData);
if (userData != null) {
setAuthUserData(userData);
if (userData.initialized) {
Expand Down
76 changes: 52 additions & 24 deletions src/app/pages/main-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ const styles = {
display: none;
}
`,
mateRecommendationIsEmpty: styled.div`
color: #000;
font-family: 'Noto Sans KR';
font-size: 1.5rem;
font-style: normal;
font-weight: 500;
line-height: normal;
display: flex;
width: 100%;
justify-content: center;
padding-block: 2rem;
`,
};

export function MainPage() {
Expand Down Expand Up @@ -144,30 +157,45 @@ export function MainPage() {
{auth?.user?.name}님의 추천 메이트
</styles.mateRecommendationTitle>
<styles.mateRecommendationRow>
<CircularButton
direction="left"
disabled={false}
onClick={handleScrollLeft}
/>
<styles.mateRecommendation ref={scrollRef}>
{recommendationMates?.data?.map(
({ memberId, score, nickname, location, profileImageUrl }) => (
<Link href={`/profile/${memberId}`} key={memberId}>
<UserCard
name={nickname}
percentage={score}
profileImage={profileImageUrl}
location={location}
/>
</Link>
),
)}
</styles.mateRecommendation>
<CircularButton
direction="right"
disabled={false}
onClick={handleScrollRight}
/>
{recommendationMates?.data != null &&
recommendationMates.data.length > 0 ? (
<>
<CircularButton
direction="left"
disabled={false}
onClick={handleScrollLeft}
/>
<styles.mateRecommendation ref={scrollRef}>
{recommendationMates.data.map(
({
memberId,
score,
nickname,
location,
profileImageUrl,
}) => (
<Link href={`/profile/${memberId}`} key={memberId}>
<UserCard
name={nickname}
percentage={score}
profileImage={profileImageUrl}
location={location}
/>
</Link>
),
)}
</styles.mateRecommendation>
<CircularButton
direction="right"
disabled={false}
onClick={handleScrollRight}
/>
</>
) : (
<styles.mateRecommendationIsEmpty style={{ alignSelf: 'center' }}>
<p>추천되는 메이트가 없습니다.</p>
</styles.mateRecommendationIsEmpty>
)}
</styles.mateRecommendationRow>
</styles.mateRecommendationContainer>
</styles.container>
Expand Down
48 changes: 34 additions & 14 deletions src/app/pages/mobile/mobile-main-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ const styles = {
overflow-x: auto;
flex-wrap: wrap;
`,
mateRecommendationIsEmpty: styled.div`
color: #000;
font-family: 'Noto Sans KR';
font-size: 1rem;
font-style: normal;
font-weight: 500;
line-height: normal;
display: flex;
width: 100%;
justify-content: center;
padding-block: 2rem;
`,
};

export function MobileMainPage() {
Expand Down Expand Up @@ -136,20 +149,27 @@ export function MobileMainPage() {
<styles.mateRecommendationTitle>
<h1>{auth?.user?.name}님의 추천 메이트</h1>
</styles.mateRecommendationTitle>
<styles.mateRecommendation>
{recommendationMates?.data?.map(
({ memberId, score, nickname, location, profileImageUrl }) => (
<Link href={`/profile/${memberId}`} key={memberId}>
<UserCard
name={nickname}
percentage={score}
location={location}
profileImage={profileImageUrl}
/>
</Link>
),
)}
</styles.mateRecommendation>
{recommendationMates?.data != null &&
recommendationMates.data.length > 0 ? (
<styles.mateRecommendation>
{recommendationMates.data.map(
({ memberId, score, nickname, location, profileImageUrl }) => (
<Link href={`/profile/${memberId}`} key={memberId}>
<UserCard
name={nickname}
percentage={score}
location={location}
profileImage={profileImageUrl}
/>
</Link>
),
)}
</styles.mateRecommendation>
) : (
<styles.mateRecommendationIsEmpty>
<p>추천되는 메이트가 없습니다.</p>
</styles.mateRecommendationIsEmpty>
)}
</styles.mateRecommendationContainer>
</styles.container>
);
Expand Down
93 changes: 45 additions & 48 deletions src/app/pages/mobile/mobile-shared-posts-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import {
useDormitorySharedPosts,
usePaging,
useSharedPosts,
type GetDormitorySharedPostsDTO,
type GetSharedPostsDTO,
} from '@/features/shared';

const styles = {
Expand Down Expand Up @@ -135,6 +133,16 @@ const styles = {
justify-content: flex-start;
}
`,
noRecommendation: styled.div`
font-family: 'Noto Sans KR';
font-size: 0.85rem;
font-style: normal;
font-weight: 500;
display: flex;
width: 100%;
justify-content: center;
`,
};

export function MobileSharedPostsPage() {
Expand All @@ -143,9 +151,6 @@ export function MobileSharedPostsPage() {
const auth = useAuthValue();
const [selected, setSelected] = useState<SharedPostsType>('hasRoom');
const [totalPageCount, setTotalPageCount] = useState(0);
const [prevSharedPosts, setPrevSharedPosts] = useState<
GetSharedPostsDTO | GetDormitorySharedPostsDTO | null
>(null);

const { filter, derivedFilter, reset: resetFilter } = useSharedPostsFilter();

Expand Down Expand Up @@ -197,10 +202,8 @@ export function MobileSharedPostsPage() {
useEffect(() => {
if (selected === 'hasRoom' && sharedPosts != null) {
setTotalPageCount(sharedPosts.data.totalPages);
setPrevSharedPosts(null);
} else if (selected === 'dormitory' && dormitorySharedPosts != null) {
setTotalPageCount(dormitorySharedPosts.data.totalPages);
setPrevSharedPosts(null);
}
}, [selected, dormitorySharedPosts, sharedPosts]);

Expand All @@ -224,37 +227,30 @@ export function MobileSharedPostsPage() {
{selected === 'hasRoom' || selected === 'dormitory' ? (
<>
<styles.posts>
{prevSharedPosts != null
? prevSharedPosts.data.content.map(post => (
<PostCard
key={post.id}
post={post}
onClick={() => {
router.push(`/shared/${post.id}`);
}}
/>
))
: posts?.data.content.map(post => (
<PostCard
key={post.id}
post={post}
onClick={() => {
router.push(
`/shared/${selected === 'hasRoom' ? 'room' : 'dormitory'}/${post.id}`,
);
}}
/>
))}
{posts?.data != null && posts.data.content.length > 0 ? (
posts?.data.content.map(post => (
<PostCard
key={post.id}
post={post}
onClick={() => {
router.push(
`/shared/${selected === 'hasRoom' ? 'room' : 'dormitory'}/${post.id}`,
);
}}
/>
))
) : (
<styles.noRecommendation>
<p>추천되는 게시글이 없습니다.</p>
</styles.noRecommendation>
)}
</styles.posts>
{posts?.data.content.length !== 0 && (
<styles.pagingRow>
<styles.CircularButton
direction="left"
disabled={isFirstPage}
onClick={() => {
if (sharedPosts != null) {
setPrevSharedPosts(sharedPosts);
}
handlePrevPage();
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
Expand All @@ -269,9 +265,6 @@ export function MobileSharedPostsPage() {
<button
type="button"
onClick={() => {
if (sharedPosts != null) {
setPrevSharedPosts(sharedPosts);
}
handleSetPage(index + 1 + currentSlice * sliceSize);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
Expand All @@ -290,9 +283,6 @@ export function MobileSharedPostsPage() {
direction="right"
disabled={isLastPage}
onClick={() => {
if (sharedPosts != null) {
setPrevSharedPosts(sharedPosts);
}
handleNextPage();
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
Expand All @@ -302,17 +292,24 @@ export function MobileSharedPostsPage() {
</>
) : (
<styles.cards>
{recommendationMates?.data?.map(
({ memberId, score, nickname, location, profileImageUrl }) => (
<Link href={`/profile/${memberId}`} key={memberId}>
<UserCard
name={nickname}
percentage={score}
location={location}
profileImage={profileImageUrl}
/>
</Link>
),
{recommendationMates?.data != null &&
recommendationMates.data.length > 0 ? (
recommendationMates.data.map(
({ memberId, score, nickname, location, profileImageUrl }) => (
<Link href={`/profile/${memberId}`} key={memberId}>
<UserCard
name={nickname}
percentage={score}
location={location}
profileImage={profileImageUrl}
/>
</Link>
),
)
) : (
<styles.noRecommendation>
<p>추천되는 메이트가 없습니다.</p>
</styles.noRecommendation>
)}
</styles.cards>
)}
Expand Down
Loading

0 comments on commit 1651628

Please sign in to comment.