From 6bb6427acffe09fcc91625890f04d9114ebe4b92 Mon Sep 17 00:00:00 2001 From: suwonthugger <127329855+suwonthugger@users.noreply.github.com> Date: Wed, 21 Aug 2024 02:35:10 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20errorBoundary=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ErrorBoundary/ApiErrorBoundary.tsx | 88 +++++++++++++++++++ .../ErrorBoundary/GlobalErrorBoundary.tsx | 70 +++++++++++++++ src/shared/constants/error.ts | 10 +++ src/shared/utils/getErrorMessage.ts | 28 ++++++ 4 files changed, 196 insertions(+) create mode 100644 src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx create mode 100644 src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx create mode 100644 src/shared/constants/error.ts create mode 100644 src/shared/utils/getErrorMessage.ts diff --git a/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx b/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx new file mode 100644 index 0000000..c348233 --- /dev/null +++ b/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx @@ -0,0 +1,88 @@ +import { Component, ComponentType, ReactNode } from 'react'; + +import { AxiosError } from 'axios'; + +import { SHOULD_HANDLE_ERROR } from '@/shared/constants/error'; + +interface FallbackProps { + error?: AxiosError; + resetError?: () => void; +} + +interface ApiErrorBoundaryProps { + children: ReactNode; + fallback: ComponentType; + handleError?: () => void; +} + +interface ApiErrorBoundaryState { + shouldHandleError: boolean; + shouldRethrow: boolean; + error: AxiosError | Error | null; +} + +class ApiErrorBoundary extends Component { + constructor(props: ApiErrorBoundaryProps) { + super(props); + this.state = { + shouldHandleError: false, + shouldRethrow: false, + error: null, + }; + } + + static getDerivedStateFromError(error: AxiosError | Error): ApiErrorBoundaryState { + // 에러를 특정 API 에러로 가정하고 처리할 수 있는지 확인 + if ( + error instanceof AxiosError && + error?.response?.status && + SHOULD_HANDLE_ERROR.includes(error?.response?.status) + ) { + return { + shouldHandleError: true, + shouldRethrow: false, + error, + }; + } + + // 처리할 수 없는 에러는 상위 에러 바운더리로 전달 + return { + shouldHandleError: false, + shouldRethrow: true, + error, + }; + } + + // retry + resetError = () => { + this.props.handleError?.(); + + this.setState({ + shouldHandleError: false, + shouldRethrow: false, + error: null, + }); + }; + + render() { + const { shouldHandleError, shouldRethrow, error } = this.state; + const { fallback: Fallback } = this.props; + + if (shouldRethrow && error) { + throw error; // 상위 Error Boundary로 에러를 전달 + } + + if (!shouldHandleError) { + return this.props.children; // 에러가 처리 대상이 아니면 원래의 UI를 그대로 렌더링 + } + + // 에러에 따라 UI를 분기 처리 + if (error instanceof AxiosError) { + return ; + } + + return null; + } +} + +export default ApiErrorBoundary; diff --git a/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx b/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx new file mode 100644 index 0000000..3e06ac2 --- /dev/null +++ b/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx @@ -0,0 +1,70 @@ +import { Component, ReactNode } from 'react'; + +import { AxiosError } from 'axios'; + +import { ERROR_CODES } from '@/shared/constants/error'; + +interface GlobalErrorBoundaryProps { + children: ReactNode; +} + +interface GlobalErrorBoundaryState { + shouldHandleError: boolean; + error: Error | AxiosError | null; +} + +class GlobalErrorBoundary extends Component { + constructor(props: GlobalErrorBoundaryProps) { + super(props); + this.state = { + shouldHandleError: false, + error: null, + }; + } + + static getDerivedStateFromError(error: Error): GlobalErrorBoundaryState { + // 에러를 캐치하고 상태를 업데이트 + return { + shouldHandleError: true, + error, + }; + } + + resetError = () => { + this.setState({ + shouldHandleError: false, + error: null, + }); + }; + + render() { + const { shouldHandleError, error } = this.state; + + if (!shouldHandleError) { + return this.props.children; + } + + // // 네트워크 에러 처리 + // if ( + // error instanceof AxiosError && + // error.response?.status && + // error.request.status === ERROR_CODES.INTERNAL_SERVER_ERROR + // ) { + // return ; + // } + + // // 서버 점검 에러 처리 + // if ( + // error instanceof AxiosError && + // error.response?.status && + // error.request.status === ERROR_CODES.SERVICE_UNAVAILABLE + // ) { + // return ; + // } + + // // 알 수 없는 에러 처리 + // return ; + } +} + +export default GlobalErrorBoundary; diff --git a/src/shared/constants/error.ts b/src/shared/constants/error.ts new file mode 100644 index 0000000..168071b --- /dev/null +++ b/src/shared/constants/error.ts @@ -0,0 +1,10 @@ +export const SHOULD_HANDLE_ERROR = [401, 403, 404, 429]; + +export const ERROR_CODES = { + UNAUTHORIZED: 401, + FORBIDDEN: 403, + NOT_FOUND: 404, + TOO_MANY_REQUESTS: 429, + INTERNAL_SERVER_ERROR: 500, + SERVICE_UNAVAILABLE: 503, +} as const; diff --git a/src/shared/utils/getErrorMessage.ts b/src/shared/utils/getErrorMessage.ts new file mode 100644 index 0000000..15eaa66 --- /dev/null +++ b/src/shared/utils/getErrorMessage.ts @@ -0,0 +1,28 @@ +import { AxiosError } from 'axios'; + +export const getErrorMessage = (error: AxiosError): string | null => { + const status = error?.response?.status; + + switch (status) { + case 401: + return '권한이 없습니다. 다시 로그인해주세요.'; + break; + case 403: + return '이 자원에 접근할 권한이 없습니다.'; + break; + case 404: + return '요청한 자원을 찾을 수 없습니다.'; + break; + case 429: + return '요청이 너무 많습니다. 잠시 후 다시 시도해주세요.'; + break; + case 500: + return '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'; + break; + case 503: + return '현재 서비스 이용이 불가능합니다. 잠시 후 다시 시도해주세요.'; + break; + default: + return `예상치 못한 오류가 발생했습니다 (상태 코드: ${status}).`; + } +}; From 1c223685ded1dea3df8fdb9385ffa1701f7e6557 Mon Sep 17 00:00:00 2001 From: suwonthugger <127329855+suwonthugger@users.noreply.github.com> Date: Thu, 22 Aug 2024 02:04:00 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EC=BF=BC=EB=A6=AC=ED=81=B4?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8=20ThrowOnError=20=EC=86=8D?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8A=B8=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 쿼리 클라이언트 ThrowOnError 속성을 true로 변경하여 에러를 throw 할수 있게 끔하였습니다. - router 폴더를 생성해 관련 파일들 한곳에서 정리하였습니다. - UI가 없어서 임시 fallback을 문구가 있는 버튼으로 대체하였습니다. --- src/App.tsx | 7 ++- src/Router.tsx | 33 ------------- src/pages/HomePage/HomePage.tsx | 5 +- src/pages/RedirectPage/index.tsx | 4 +- src/router/Router.tsx | 48 +++++++++++++++++++ .../router.ts => router/routesConfig.ts} | 6 ++- src/shared/apis/client.ts | 7 +-- src/shared/apis/queryClient.ts | 12 +++++ .../ErrorBoundary/ApiErrorBoundary.tsx | 19 +++++--- .../ErrorBoundary/GlobalErrorBoundary.tsx | 12 ++++- 10 files changed, 101 insertions(+), 52 deletions(-) delete mode 100644 src/Router.tsx create mode 100644 src/router/Router.tsx rename src/{shared/constants/router.ts => router/routesConfig.ts} (55%) create mode 100644 src/shared/apis/queryClient.ts diff --git a/src/App.tsx b/src/App.tsx index d73eec7..a8d33e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,8 @@ import { RouterProvider } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { router } from './Router'; +import router from './router/Router'; +import GlobalErrorBoundary from './shared/components/ErrorBoundary/GlobalErrorBoundary'; const queryClient = new QueryClient(); @@ -12,7 +13,9 @@ const App = () => { return ( - + + + ); diff --git a/src/Router.tsx b/src/Router.tsx deleted file mode 100644 index e6c968e..0000000 --- a/src/Router.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { Router } from '@remix-run/router'; - -import { createBrowserRouter } from 'react-router-dom'; - -import HomePage from '@/pages/HomePage/HomePage'; -import LoginPage from '@/pages/LoginPage'; -import TimerPage from '@/pages/TimerPage/TimerPage'; - -import RedirectPage from './pages/RedirectPage'; -import { ROUTES } from './shared/constants/router'; -import withAuthProtection from './shared/hocs/withAuthProtection'; - -const ProtectedHomePage = withAuthProtection(HomePage); -const ProtectedTimerPage = withAuthProtection(TimerPage); - -export const router: Router = createBrowserRouter([ - { - path: ROUTES.login.path, - element: , - }, - { - path: ROUTES.home.path, - element: , - }, - { - path: ROUTES.timer.path, - element: , - }, - { - path: ROUTES.redirect.path, - element: , - }, -]); diff --git a/src/pages/HomePage/HomePage.tsx b/src/pages/HomePage/HomePage.tsx index 246d661..0d02efc 100644 --- a/src/pages/HomePage/HomePage.tsx +++ b/src/pages/HomePage/HomePage.tsx @@ -19,13 +19,12 @@ import { getDailyCategoryTask, isTaskExist, splitTasksByCompletion } from '@/sha import { Task } from '@/shared/types/home'; -import { ROUTES } from '@/shared/constants/router'; - import BellIcon from '@/shared/assets/svgs/bell.svg?react'; import FriendSettingIcon from '@/shared/assets/svgs/friend_setting.svg?react'; import LargePlusIcon from '@/shared/assets/svgs/large_plus.svg?react'; import HomePageWrapper from '@/components/templates/HomePageWrapper'; +import { ROUTES_CONFIG } from '@/router/routesConfig'; import ButtonSVG from '../../shared/components/ButtonSVG'; import BoxCategory from './components/BoxCategory'; @@ -126,7 +125,7 @@ const HomePage = () => { createTodayTodos(dataToPost, { onSuccess: () => { - navigate(ROUTES.timer.path); + navigate(ROUTES_CONFIG.timer.path); }, }); }; diff --git a/src/pages/RedirectPage/index.tsx b/src/pages/RedirectPage/index.tsx index da4de17..af37cf4 100644 --- a/src/pages/RedirectPage/index.tsx +++ b/src/pages/RedirectPage/index.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; // import { useLocation } from 'react-router-dom'; // import { useSignUp } from '@/shared/apis/auth/queries'; -import { ROUTES } from '@/shared/constants/router'; +import { ROUTES_CONFIG } from '@/router/routesConfig'; const RedirectPage = () => { //Todo: 서버 이슈로 로그인 관련 로직 앱잼 끝나고 사용 @@ -29,7 +29,7 @@ const RedirectPage = () => { // }, [error, navigate, data]); useEffect(() => { - navigate(ROUTES.home.path, { replace: true }); + navigate(ROUTES_CONFIG.home.path, { replace: true }); }, [navigate]); return
RedirectPage
; diff --git a/src/router/Router.tsx b/src/router/Router.tsx new file mode 100644 index 0000000..bba8a8e --- /dev/null +++ b/src/router/Router.tsx @@ -0,0 +1,48 @@ +import type { Router } from '@remix-run/router'; + +import { Outlet, createBrowserRouter } from 'react-router-dom'; + +import HomePage from '@/pages/HomePage/HomePage'; +import LoginPage from '@/pages/LoginPage'; +import TimerPage from '@/pages/TimerPage/TimerPage'; + +import RedirectPage from '../pages/RedirectPage'; +import { ROUTES_CONFIG } from './routesConfig'; + +const ProtectedRoute = () => { + //Todo: 개발이 진행되면 실제 토큰 상태를 받아서 login page로 이동 시킴 + // const accessToken = getAccessTotken(); + // if (!accessToken) { + // alert('로그인 해주세요'); + // return ; + // } + return ; +}; + +const router: Router = createBrowserRouter([ + { + path: ROUTES_CONFIG.login.path, + element: , + }, + { + path: ROUTES_CONFIG.redirect.path, + element: , + }, + + { + path: '/', + element: , + children: [ + { + path: ROUTES_CONFIG.home.path, + element: , + }, + { + path: ROUTES_CONFIG.timer.path, + element: , + }, + ], + }, +]); + +export default router; diff --git a/src/shared/constants/router.ts b/src/router/routesConfig.ts similarity index 55% rename from src/shared/constants/router.ts rename to src/router/routesConfig.ts index 811bde4..62bf68d 100644 --- a/src/shared/constants/router.ts +++ b/src/router/routesConfig.ts @@ -1,14 +1,18 @@ -export const ROUTES = { +export const ROUTES_CONFIG = { login: { + title: 'Login', path: '/', }, home: { + title: 'Home', path: '/home', }, timer: { + title: 'Timer', path: '/timer', }, redirect: { + title: 'Redirect', path: '/redirect', }, }; diff --git a/src/shared/apis/client.ts b/src/shared/apis/client.ts index 2232937..746dd8b 100644 --- a/src/shared/apis/client.ts +++ b/src/shared/apis/client.ts @@ -1,8 +1,9 @@ import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; -import { ROUTES } from '@/shared/constants/router'; import { getAccessTotken } from '@/shared/utils/token'; +import { ROUTES_CONFIG } from '@/router/routesConfig'; + // import { reissueToken } from './auth/axios'; const API_URL = `${import.meta.env.VITE_BASE_URL}`; @@ -29,7 +30,7 @@ const addAuthInterceptor = (axiosClient: AxiosInstance) => { axiosClient.interceptors.request.use(async (config) => { const accessToken = getAccessTotken(); if (!accessToken) { - window.location.href = ROUTES.login.path; + window.location.href = ROUTES_CONFIG.login.path; } config.headers.Authorization = `Bearer ${accessToken}`; @@ -47,7 +48,7 @@ const addAuthInterceptor = (axiosClient: AxiosInstance) => { // try { // reissueToken(); // } catch (reissueError) { - // window.location.href = ROUTES.login.path; + // window.location.href = ROUTES_CONFIG.login.path; // } // } // return Promise.reject(e); diff --git a/src/shared/apis/queryClient.ts b/src/shared/apis/queryClient.ts new file mode 100644 index 0000000..5305ce5 --- /dev/null +++ b/src/shared/apis/queryClient.ts @@ -0,0 +1,12 @@ +import { QueryClient } from '@tanstack/react-query'; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + throwOnError: true, + }, + mutations: { + throwOnError: true, + }, + }, +}); diff --git a/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx b/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx index c348233..714ccca 100644 --- a/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx +++ b/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx @@ -11,7 +11,7 @@ interface FallbackProps { interface ApiErrorBoundaryProps { children: ReactNode; - fallback: ComponentType; + fallback?: ComponentType; handleError?: () => void; } @@ -65,8 +65,9 @@ class ApiErrorBoundary extends Component; + // } + if (error instanceof AxiosError) { - return ; + return ( + + ); } - - return null; } } diff --git a/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx b/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx index 3e06ac2..cdd28d6 100644 --- a/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx +++ b/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx @@ -2,7 +2,7 @@ import { Component, ReactNode } from 'react'; import { AxiosError } from 'axios'; -import { ERROR_CODES } from '@/shared/constants/error'; +// import { ERROR_CODES } from '@/shared/constants/error'; interface GlobalErrorBoundaryProps { children: ReactNode; @@ -44,6 +44,7 @@ class GlobalErrorBoundary extends Component; // } - // // 알 수 없는 에러 처리 + // 알 수 없는 에러 처리 // return ; + + if (error) + return ( + + ); } } From ce580af42f8e5ae111334bb491fdd70a898d7ad2 Mon Sep 17 00:00:00 2001 From: suwonthugger <127329855+suwonthugger@users.noreply.github.com> Date: Thu, 22 Aug 2024 02:11:38 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9A=B0=ED=8A=B8=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EA=B5=AC=EC=A1=B0=20=EB=AA=85=ED=99=95?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20404?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/router/Router.tsx | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/router/Router.tsx b/src/router/Router.tsx index bba8a8e..2a360b8 100644 --- a/src/router/Router.tsx +++ b/src/router/Router.tsx @@ -21,15 +21,23 @@ const ProtectedRoute = () => { const router: Router = createBrowserRouter([ { - path: ROUTES_CONFIG.login.path, - element: , - }, - { - path: ROUTES_CONFIG.redirect.path, - element: , + //public 라우트들 + path: '/', + element: , + children: [ + { + path: ROUTES_CONFIG.login.path, + element: , + }, + { + path: ROUTES_CONFIG.redirect.path, + element: , + }, + ], }, { + //권한이 있어야 접근 가능한 라우트들 path: '/', element: , children: [ @@ -43,6 +51,12 @@ const router: Router = createBrowserRouter([ }, ], }, + + { + //404 페이지 + path: '*', + element:
잘못 찾아오셨어요!
, + }, ]); export default router; From e1702dcfda27d33215b0fd45763838d488476196 Mon Sep 17 00:00:00 2001 From: suwonthugger <127329855+suwonthugger@users.noreply.github.com> Date: Thu, 22 Aug 2024 03:01:31 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B0=98=EC=98=81=ED=95=98=EC=97=AC=20prettier=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc | 1 + src/pages/HomePage/HomePage.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index a68fa81..436487d 100644 --- a/.prettierrc +++ b/.prettierrc @@ -27,6 +27,7 @@ "^@/shared/types/(.*)$", "^@/shared/constants/(.*)$", "^@/shared/assets/(.*)$", + "^@/router/(.*)$", "^@/(.*)$", "^[./]" ], diff --git a/src/pages/HomePage/HomePage.tsx b/src/pages/HomePage/HomePage.tsx index 0d02efc..1b453e5 100644 --- a/src/pages/HomePage/HomePage.tsx +++ b/src/pages/HomePage/HomePage.tsx @@ -23,9 +23,10 @@ import BellIcon from '@/shared/assets/svgs/bell.svg?react'; import FriendSettingIcon from '@/shared/assets/svgs/friend_setting.svg?react'; import LargePlusIcon from '@/shared/assets/svgs/large_plus.svg?react'; -import HomePageWrapper from '@/components/templates/HomePageWrapper'; import { ROUTES_CONFIG } from '@/router/routesConfig'; +import HomePageWrapper from '@/components/templates/HomePageWrapper'; + import ButtonSVG from '../../shared/components/ButtonSVG'; import BoxCategory from './components/BoxCategory'; import BoxTodayTodo from './components/BoxTodayTodo'; From 5766ef6602f175a5b454e9db31257da6aaad4f1f Mon Sep 17 00:00:00 2001 From: suwonthugger <127329855+suwonthugger@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:19:40 +0900 Subject: [PATCH 5/8] =?UTF-8?q?style:=20404=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=97=B0=EB=8F=99=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/NotFoundPage/NotFoundPage.tsx | 44 +++++++++++++++++++++++++ src/router/Router.tsx | 3 +- src/shared/assets/svgs/404.svg | 14 ++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/pages/NotFoundPage/NotFoundPage.tsx create mode 100644 src/shared/assets/svgs/404.svg diff --git a/src/pages/NotFoundPage/NotFoundPage.tsx b/src/pages/NotFoundPage/NotFoundPage.tsx new file mode 100644 index 0000000..1c5aa2d --- /dev/null +++ b/src/pages/NotFoundPage/NotFoundPage.tsx @@ -0,0 +1,44 @@ +import { useNavigate } from 'react-router-dom'; + +import { HomeLargeBtnVariant } from '@/shared/types/global'; + +import NotFoundIcon from '@/shared/assets/svgs/404.svg?react'; +import BellIcon from '@/shared/assets/svgs/bell.svg?react'; +import FriendSettingIcon from '@/shared/assets/svgs/friend_setting.svg?react'; + +import HomeLargeBtn from '@/components/atoms/HomeLargeBtn'; + +import SideBarHome from '../HomePage/components/SideBarHome'; + +const NotFoundPage = () => { + const navigate = useNavigate(); + + return ( +
+ + +
+ + +
+ +
+ +

페이지를 찾을 수 없습니다.

+

올바른 URL을 입력하였는지 확인하세요.

+ +
+ navigate('/home')} variant={HomeLargeBtnVariant.LARGE}> + 홈으로 돌아가기 + +
+
+
+ ); +}; + +export default NotFoundPage; diff --git a/src/router/Router.tsx b/src/router/Router.tsx index 2a360b8..f07a4a1 100644 --- a/src/router/Router.tsx +++ b/src/router/Router.tsx @@ -4,6 +4,7 @@ import { Outlet, createBrowserRouter } from 'react-router-dom'; import HomePage from '@/pages/HomePage/HomePage'; import LoginPage from '@/pages/LoginPage'; +import NotFoundPage from '@/pages/NotFoundPage/NotFoundPage'; import TimerPage from '@/pages/TimerPage/TimerPage'; import RedirectPage from '../pages/RedirectPage'; @@ -55,7 +56,7 @@ const router: Router = createBrowserRouter([ { //404 페이지 path: '*', - element:
잘못 찾아오셨어요!
, + element: , }, ]); diff --git a/src/shared/assets/svgs/404.svg b/src/shared/assets/svgs/404.svg new file mode 100644 index 0000000..a32a879 --- /dev/null +++ b/src/shared/assets/svgs/404.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + From 3ee5830019b27c60bf88f4749e08fd9471f9bb9a Mon Sep 17 00:00:00 2001 From: suwonthugger <127329855+suwonthugger@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:52:44 +0900 Subject: [PATCH 6/8] =?UTF-8?q?style:=20=ED=99=88=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20Error=20UI=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/HomePage/HomePage.tsx | 18 ++++++++++-------- src/shared/assets/svgs/error.svg | 11 +++++++++++ src/shared/components/Error.tsx | 29 +++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 src/shared/assets/svgs/error.svg create mode 100644 src/shared/components/Error.tsx diff --git a/src/pages/HomePage/HomePage.tsx b/src/pages/HomePage/HomePage.tsx index 1b453e5..7abe393 100644 --- a/src/pages/HomePage/HomePage.tsx +++ b/src/pages/HomePage/HomePage.tsx @@ -148,6 +148,16 @@ const HomePage = () => {
+ +
+ + +
+
@@ -216,14 +226,6 @@ const HomePage = () => {
-
- - - - - - -
+ + + + + + + + + + diff --git a/src/shared/components/Error.tsx b/src/shared/components/Error.tsx new file mode 100644 index 0000000..1234cef --- /dev/null +++ b/src/shared/components/Error.tsx @@ -0,0 +1,29 @@ +import { HomeLargeBtnVariant } from '@/shared/types/global'; + +import ErrorIcon from '@/shared/assets/svgs/error.svg?react'; + +import HomeLargeBtn from '@/components/atoms/HomeLargeBtn'; + +interface ErrorProps { + resetError: () => void; +} + +const Error = ({ resetError }: ErrorProps) => { + return ( +
+
+ +

일시적인 오류가 발생했습니다.

+

잠시 후 다시 이용해 주세요.

+ +
+ + 다시 시도하기 + +
+
+
+ ); +}; + +export default Error; From 6917ec681fdcb01a914060c03fa387081eb60970 Mon Sep 17 00:00:00 2001 From: suwonthugger <127329855+suwonthugger@users.noreply.github.com> Date: Tue, 17 Sep 2024 04:10:42 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20ApiErrorFallback=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EB=B0=94=EC=9A=B4=EB=8D=94=EB=A6=AC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 6 +++--- .../{Error.tsx => ApiErrorFallback.tsx} | 4 ++-- .../ErrorBoundary/ApiErrorBoundary.tsx | 18 ++++-------------- .../ErrorBoundary/GlobalErrorBoundary.tsx | 9 +++------ 4 files changed, 12 insertions(+), 25 deletions(-) rename src/shared/components/{Error.tsx => ApiErrorFallback.tsx} (89%) diff --git a/src/App.tsx b/src/App.tsx index a8d33e1..ff30289 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,13 +2,13 @@ import { Provider } from 'jotai'; import { RouterProvider } from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClientProvider } from '@tanstack/react-query'; + +import { queryClient } from '@/shared/apis/queryClient'; import router from './router/Router'; import GlobalErrorBoundary from './shared/components/ErrorBoundary/GlobalErrorBoundary'; -const queryClient = new QueryClient(); - const App = () => { return ( diff --git a/src/shared/components/Error.tsx b/src/shared/components/ApiErrorFallback.tsx similarity index 89% rename from src/shared/components/Error.tsx rename to src/shared/components/ApiErrorFallback.tsx index 1234cef..01a2a44 100644 --- a/src/shared/components/Error.tsx +++ b/src/shared/components/ApiErrorFallback.tsx @@ -8,7 +8,7 @@ interface ErrorProps { resetError: () => void; } -const Error = ({ resetError }: ErrorProps) => { +const ApiErrorFallback = ({ resetError }: ErrorProps) => { return (
@@ -26,4 +26,4 @@ const Error = ({ resetError }: ErrorProps) => { ); }; -export default Error; +export default ApiErrorFallback; diff --git a/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx b/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx index 714ccca..bf96400 100644 --- a/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx +++ b/src/shared/components/ErrorBoundary/ApiErrorBoundary.tsx @@ -6,12 +6,12 @@ import { SHOULD_HANDLE_ERROR } from '@/shared/constants/error'; interface FallbackProps { error?: AxiosError; - resetError?: () => void; + resetError: () => void; } interface ApiErrorBoundaryProps { children: ReactNode; - fallback?: ComponentType; + fallback: ComponentType; handleError?: () => void; } @@ -32,7 +32,6 @@ class ApiErrorBoundary extends Component; - // } - if (error instanceof AxiosError) { - return ( - - ); + return ; } } } diff --git a/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx b/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx index cdd28d6..b190bcb 100644 --- a/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx +++ b/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx @@ -2,6 +2,8 @@ import { Component, ReactNode } from 'react'; import { AxiosError } from 'axios'; +import ApiErrorFallback from '../ApiErrorFallback'; + // import { ERROR_CODES } from '@/shared/constants/error'; interface GlobalErrorBoundaryProps { @@ -66,12 +68,7 @@ class GlobalErrorBoundary extends Component; - if (error) - return ( - - ); + if (error) return ; } } From e9b454372aa7f7e2969dab456c19da1b36cdec55 Mon Sep 17 00:00:00 2001 From: suwonthugger <127329855+suwonthugger@users.noreply.github.com> Date: Tue, 17 Sep 2024 04:39:42 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx | 4 ++-- .../components/{ApiErrorFallback.tsx => FallbackApiError.tsx} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/shared/components/{ApiErrorFallback.tsx => FallbackApiError.tsx} (89%) diff --git a/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx b/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx index b190bcb..c5f3a08 100644 --- a/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx +++ b/src/shared/components/ErrorBoundary/GlobalErrorBoundary.tsx @@ -2,7 +2,7 @@ import { Component, ReactNode } from 'react'; import { AxiosError } from 'axios'; -import ApiErrorFallback from '../ApiErrorFallback'; +import FallbackApiError from '../FallbackApiError'; // import { ERROR_CODES } from '@/shared/constants/error'; @@ -68,7 +68,7 @@ class GlobalErrorBoundary extends Component; - if (error) return ; + if (error) return ; } } diff --git a/src/shared/components/ApiErrorFallback.tsx b/src/shared/components/FallbackApiError.tsx similarity index 89% rename from src/shared/components/ApiErrorFallback.tsx rename to src/shared/components/FallbackApiError.tsx index 01a2a44..d6da616 100644 --- a/src/shared/components/ApiErrorFallback.tsx +++ b/src/shared/components/FallbackApiError.tsx @@ -8,7 +8,7 @@ interface ErrorProps { resetError: () => void; } -const ApiErrorFallback = ({ resetError }: ErrorProps) => { +const FallbackApiError = ({ resetError }: ErrorProps) => { return (
@@ -26,4 +26,4 @@ const ApiErrorFallback = ({ resetError }: ErrorProps) => { ); }; -export default ApiErrorFallback; +export default FallbackApiError;