Skip to content

Commit 0633f8f

Browse files
committed
Merge branch 'main' into develop
2 parents 4cb559f + 2c3939f commit 0633f8f

File tree

6 files changed

+146
-53
lines changed

6 files changed

+146
-53
lines changed

src/components/common/ErrorBoundary/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { ErrorInfo } from 'react';
22
import showToast from 'utils/ts/showToast';
33
import { AxiosError } from 'axios';
4+
import { sendClientError } from '@bcsdlab/koin';
45

56
interface Props {
67
fallbackClassName: string;
@@ -33,6 +34,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
3334
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3435
componentDidCatch(error: AxiosError<any, any> | Error, __: ErrorInfo) {
3536
showToast('error', isAxiosError(error) ? error.response?.data.error.message : error.message);
37+
sendClientError(error);
3638
}
3739

3840
render() {

src/main.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import './index.scss';
44
import { BrowserRouter } from 'react-router-dom';
55
import PortalProvider from 'components/common/Modal/PortalProvider';
66
import { RecoilRoot } from 'recoil';
7-
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
8-
import { sendClientError } from '@bcsdlab/koin';
7+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
98
import App from './App';
109
import reportWebVitals from './reportWebVitals';
1110

@@ -16,9 +15,6 @@ const queryClient = new QueryClient({
1615
retry: false,
1716
},
1817
},
19-
queryCache: new QueryCache({
20-
onError: (error) => sendClientError(error),
21-
}),
2218
});
2319

2420
const root = ReactDOM.createRoot(

src/pages/Store/StoreDetailPage/index.tsx

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef } from 'react';
1+
import React, { Suspense, useEffect, useRef } from 'react';
22
import getDayOfWeek from 'utils/ts/getDayOfWeek';
33
import ImageModal from 'components/common/Modal/ImageModal';
44
import {
@@ -21,6 +21,8 @@ import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
2121
import * as api from 'api';
2222
import useTokenState from 'utils/hooks/state/useTokenState';
2323
import { useABTestView } from 'utils/hooks/abTest/useABTestView';
24+
// eslint-disable-next-line
25+
import StoreDetailBoundary from '../components/StoreDetailBoundary/StoreDetailBoundary';
2426
import MenuTable from './MenuTable';
2527
import EventTable from './EventTable';
2628
import styles from './StoreDetailPage.module.scss';
@@ -37,7 +39,7 @@ function StoreDetailPage() {
3739
const logger = useLogger();
3840
// waterfall 현상 막기
3941
const { data: paralleData } = useSuspenseQuery({
40-
queryKey: ['storeDetail', 'storeDetailMenu', 'review'],
42+
queryKey: ['storeDetail', 'storeDetailMenu', 'review', params.id],
4143
queryFn: () => Promise.all([
4244
queryClient.fetchQuery({
4345
queryKey: ['storeDetail', params.id],
@@ -410,18 +412,29 @@ function StoreDetailPage() {
410412
{tapType === '리뷰' && <ReviewPage id={params.id!} />}
411413
</div>
412414
{testValue === 'call_floating' && (
413-
<a
414-
role="button"
415-
aria-label="상점 전화하기"
416-
href={`tel:${storeDetail?.phone}`}
417-
onClick={onClickCallNumber}
418-
className={styles['phone-button--floating']}
419-
>
420-
<Phone />
421-
</a>
415+
<a
416+
role="button"
417+
aria-label="상점 전화하기"
418+
href={`tel:${storeDetail?.phone}`}
419+
onClick={onClickCallNumber}
420+
className={styles['phone-button--floating']}
421+
>
422+
<Phone />
423+
</a>
422424
)}
423425
</div>
424426
);
425427
}
426428

427-
export default StoreDetailPage;
429+
function StoreDetail() {
430+
const navigate = useNavigate();
431+
return (
432+
<StoreDetailBoundary onErrorClick={() => navigate('/store')}>
433+
<Suspense fallback={<div />}>
434+
<StoreDetailPage />
435+
</Suspense>
436+
</StoreDetailBoundary>
437+
);
438+
}
439+
440+
export default StoreDetail;

src/pages/Store/StorePage/index.tsx

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,32 @@ function StorePage() {
137137
localStorage.setItem('store-review-tooltip', 'used');
138138
closeTooltip();
139139
};
140-
const navigation = useNavigate();
140+
const navigate = useNavigate();
141+
142+
const handleCategoryClick = (categoryId: number) => {
143+
logger.actionEventClick({
144+
actionTitle: 'BUSINESS',
145+
event_label: 'shop_categories',
146+
value: categoryId.toString(),
147+
event_category: 'click',
148+
previous_page:
149+
categories.shop_categories.find(
150+
(item) => item.id === Number(searchParams.get('category')),
151+
)?.name || '전체보기',
152+
duration_time:
153+
(new Date().getTime()
154+
- Number(sessionStorage.getItem('enter_category')))
155+
/ 1000,
156+
current_page: categoryId.toString(),
157+
});
158+
159+
sessionStorage.setItem('enter_category', new Date().getTime().toString());
160+
161+
setParams('category', `${categoryId}`, {
162+
deleteBeforeParam: false,
163+
replacePage: false,
164+
});
165+
};
141166

142167
const koreanCategory = selectedCategory === -1
143168
? '전체보기'
@@ -251,40 +276,7 @@ function StorePage() {
251276
role="radio"
252277
aria-checked={category.id === selectedCategory}
253278
type="button"
254-
onClick={() => {
255-
logger.actionEventClick({
256-
actionTitle: 'BUSINESS',
257-
event_label: 'shop_categories',
258-
value: category.name,
259-
event_category: 'click',
260-
previous_page:
261-
categories.shop_categories.find(
262-
(item) => item.id === Number(searchParams.get('category')),
263-
)?.name || '전체보기',
264-
duration_time:
265-
(new Date().getTime()
266-
- Number(sessionStorage.getItem('enter_category')))
267-
/ 1000,
268-
current_page: category.name,
269-
});
270-
sessionStorage.setItem(
271-
'enter_category',
272-
new Date().getTime().toString(),
273-
);
274-
275-
setParams('category', `${category.id} `, {
276-
deleteBeforeParam: false,
277-
replacePage: true,
278-
});
279-
setParams('shopIds', '', {
280-
deleteBeforeParam: true,
281-
replacePage: true,
282-
});
283-
setParams('searchWord', '', {
284-
deleteBeforeParam: true,
285-
replacePage: true,
286-
});
287-
}}
279+
onClick={() => handleCategoryClick(category.id)}
288280
key={category.id}
289281
>
290282
<img
@@ -298,7 +290,7 @@ function StorePage() {
298290
</div>
299291
<button
300292
type="button"
301-
onClick={() => navigation(`${ROUTES.BenefitStore()}?category=1`)}
293+
onClick={() => navigate(`${ROUTES.BenefitStore()}?category=1`)}
302294
className={styles.category__benefit}
303295
>
304296
혜택이 있는 상점 모아보기
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.container {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
flex-direction: column;
6+
height: 60vh;
7+
}
8+
9+
.button {
10+
color: #fff;
11+
font-size: 15px;
12+
text-decoration: none;
13+
cursor: pointer;
14+
width: 88px;
15+
height: 36px;
16+
box-sizing: border-box;
17+
display: flex;
18+
justify-content: center;
19+
align-items: center;
20+
background-color: #175c8e;
21+
margin-top: 25px;
22+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React, { ErrorInfo } from 'react';
2+
import { AxiosError } from 'axios';
3+
import showToast from 'utils/ts/showToast';
4+
import { isKoinError, sendClientError } from '@bcsdlab/koin';
5+
import styles from './StoreDetailBoundary.module.scss';
6+
7+
interface Props {
8+
onErrorClick: () => void;
9+
children: React.ReactNode;
10+
}
11+
12+
interface State {
13+
hasError: boolean;
14+
status?: number;
15+
}
16+
17+
function isAxiosError(error: AxiosError<any, any> | Error): error is AxiosError<any, any> {
18+
return ('response' in error);
19+
}
20+
21+
export default class StoreDetailBoundary extends React.Component<Props, State> {
22+
constructor(props: Props) {
23+
super(props);
24+
this.state = { hasError: false };
25+
}
26+
27+
// 이후에 사용시 해제
28+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
29+
static getDerivedStateFromError(error: Error) {
30+
if (isKoinError(error) || isAxiosError(error)) {
31+
return { hasError: true, status: error.status };
32+
}
33+
return { hasError: true };
34+
}
35+
36+
// 이후에 사용시 해제
37+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
38+
componentDidCatch(error: Error, __: ErrorInfo) {
39+
showToast('error', error.message);
40+
sendClientError(error);
41+
}
42+
43+
render() {
44+
const { children, onErrorClick } = this.props;
45+
const { hasError, status } = this.state;
46+
47+
if (hasError && status === 404) {
48+
return (
49+
<div className={styles.container}>
50+
<h1>존재하지 않는 상점입니다.</h1>
51+
<button
52+
className={styles.button}
53+
type="button"
54+
onClick={onErrorClick}
55+
>
56+
상점 목록
57+
</button>
58+
</div>
59+
);
60+
}
61+
62+
if (hasError) {
63+
return <div className={styles.container}>오류가 발생했습니다</div>;
64+
}
65+
66+
return children;
67+
}
68+
}

0 commit comments

Comments
 (0)