diff --git a/package.json b/package.json index c5f4747..f1e27cb 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,14 @@ "nanoid": "^5.0.7", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.13", "react-hook-form": "^7.50.1", "react-router-dom": "^6.22.1", "react-toastify": "^10.0.4", "wowds-icons": "^0.1.0", "wowds-tokens": "^0.0.9", - "zustand": "^4.5.0", - "wowds-ui": "^0.1.9" + "wowds-ui": "^0.1.9", + "zustand": "^4.5.0" }, "devDependencies": { "@sentry/react": "^8.22.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc189e9..5c34b68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-error-boundary: + specifier: ^4.0.13 + version: 4.0.13(react@18.2.0) react-hook-form: specifier: ^7.50.1 version: 7.50.1(react@18.2.0) @@ -8808,6 +8811,15 @@ packages: react-is: 18.1.0 dev: true + /react-error-boundary@4.0.13(react@18.2.0): + resolution: {integrity: sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==} + peerDependencies: + react: '>=16.13.1' + dependencies: + '@babel/runtime': 7.23.9 + react: 18.2.0 + dev: false + /react-hook-form@7.50.1(react@18.2.0): resolution: {integrity: sha512-3PCY82oE0WgeOgUtIr3nYNNtNvqtJ7BZjsbxh6TnYNbXButaD5WpjOmTjdxZfheuHKR68qfeFnEDVYoSSFPMTQ==} engines: {node: '>=12.22.0'} diff --git a/public/notfound.png b/public/notfound.png new file mode 100644 index 0000000..3385fa5 Binary files /dev/null and b/public/notfound.png differ diff --git a/src/App.tsx b/src/App.tsx index 06d163e..9a801f7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -67,12 +67,14 @@ function App() { word-break: keep-all; `}> GDSC는 Google Developers에서 제공하는 프로그램을 통해 운영되는 대학교 - 기반의 개발자 커뮤니티 그룹입니다.
+ 기반의 개발자 커뮤니티 그룹이에요. +
개발자로서 성장하는 데 관심이 있는 학부생이라면 누구나 참여할 수 - 있습니다.
+ 있어요. +
GDSC의 목표는 전 세계의 대학생 개발자들이 구글 관련 기술 혹은 프로그래밍 실력을 높이고 지역 사회와 협력하여 실제 문제를 해결하도록 - 성장하는 것입니다. + 성장하는 것이에요. @@ -97,10 +99,12 @@ function App() { word-break: keep-all; `}> GDSC Hongik Open Community는 학생 개발자를 위한 홍익대학교의 개발 - 커뮤니티입니다. -
초심자들이 개발에 관심을 가지고 입문할 수 있도록 기초 학술 - 프로그램을 운영하며, 다양한 이벤트와 컨텐츠를 통해 학회원들과 소통할 수 - 있는 네트워킹 플랫폼을 제공하고 있습니다. + 커뮤니티예요. +
+ “더 나은 환경을, 더 나은 학우에게” 제공하는 것을 목표로, 초심자들이 + 개발에 관심을 가지고 입문할 수 있도록 기초 학술 프로그램을 운영하며, + 다양한 이벤트와 컨텐츠를 통해 학회원들과 소통할 수 있는 네트워킹 + 플랫폼을 제공하고 있어요. @@ -114,9 +118,8 @@ function App() { text-align: center; word-break: keep-all; `}> - GDSC의 목표는 전 세계의 대학생 개발자들이 구글 관련 기술 혹은 프로그래밍 - 실력을 높이고 지역 사회와 협력하여 실제 문제를 해결하도록 성장하는 - 것입니다. + 이번 학기 GDSC Hongik은 변화와 발전에 초점을 두어, 현재에 안주하지 않고 + 변화를 통해 구성원이 함께 발전시켜나갈 수 있는 커뮤니티를 운영하려 해요. @@ -132,17 +135,14 @@ function App() { text-align: center; word-break: keep-all; `}> - GDSC의 이러한 비전에 맞추어, GDSC Hongik은 초심자들이 개발에 관심을 - 가지고 입문할 수 있도록 기초 학술 프로그램을 운영하며, 다양한 이벤트와 - 콘텐츠를 통해 학회원들과 소통할 수 있는 네트워킹 플랫폼을 제공하고 - 있습니다. + 초심자들을 위한 기초 학술 프로그램부터, 다양한 이벤트와 컨텐츠를 통해 + 학회원들과 소통할 수 있는 네트워킹 플랫폼을 제공할 예정이에요. @@ -178,17 +177,23 @@ function App() { + diff --git a/src/components/ApiErrorBoundary.tsx b/src/components/ApiErrorBoundary.tsx index d0efcd9..933efbf 100644 --- a/src/components/ApiErrorBoundary.tsx +++ b/src/components/ApiErrorBoundary.tsx @@ -1,17 +1,21 @@ -import { PropsWithChildren } from 'react'; - +import * as Sentry from '@sentry/react'; import { useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { redirect } from 'react-router-dom'; import { toast } from 'react-toastify'; import RoutePath from '@/routes/routePath'; +import { ReactNode } from 'react'; type ErrorResponseType = { errorCodeName: string; errorMessage: string; }; -export default function ApiErrorBoundary({ children }: PropsWithChildren) { +export default function ApiErrorBoundary({ + children +}: { + children: ReactNode; +}) { const queryClient = useQueryClient(); queryClient.getQueryCache().config = { @@ -24,7 +28,10 @@ export default function ApiErrorBoundary({ children }: PropsWithChildren) { function handleError(axiosError: AxiosError) { const errorResponse = axiosError.response?.data as ErrorResponseType; - + if (errorResponse) { + // eslint-disable-next-line import/namespace + Sentry.captureException(errorResponse, {}); + } const message = errorResponse.errorMessage; switch (axiosError.response?.status) { @@ -38,6 +45,11 @@ export default function ApiErrorBoundary({ children }: PropsWithChildren) { toast.error(message); break; } + + if (errorResponse) { + // eslint-disable-next-line import/namespace + Sentry.captureException(errorResponse, {}); + } } return <>{children}; diff --git a/src/components/common/AsyncBoundary.tsx b/src/components/common/AsyncBoundary.tsx new file mode 100644 index 0000000..7b0793a --- /dev/null +++ b/src/components/common/AsyncBoundary.tsx @@ -0,0 +1,15 @@ +import { ErrorBoundary } from '@sentry/react'; +import { Suspense, SuspenseProps } from 'react'; +import LoadingSpinner from './LoadingSpinner'; + +type AsyncBoundaryProps = Omit; + +const AsyncBoundary = ({ children, ...rest }: AsyncBoundaryProps) => { + return ( + + }>{children} + + ); +}; + +export default AsyncBoundary; diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 50e7d10..a355654 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -27,6 +27,11 @@ const Footer = () => { GDSC Hongik 가이드라인{' '} + + + 와우온보딩 이용약관 + + diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx new file mode 100644 index 0000000..c6c3619 --- /dev/null +++ b/src/pages/NotFound.tsx @@ -0,0 +1,73 @@ +import { Flex, Space, Text } from '@/components/common/Wrapper'; +import * as Sentry from '@sentry/react'; +import GlobalSize from '@/constants/globalSize'; +import { media } from '@/styles'; +import styled from '@emotion/styled'; +import { useNavigate } from 'react-router-dom'; +import { color } from 'wowds-tokens'; +import Button from 'wowds-ui/Button'; + +const NotFoundPage = () => { + const navigate = useNavigate(); + if (process.env.NODE_ENV === 'production') { + // eslint-disable-next-line import/namespace + Sentry.captureMessage('404 Page Not Found', { + extra: { + pathname: location.pathname + } + }); + } + return ( + + + + + 오류가 발생했어요. + 요청하신 페이지를 찾을 수 없어요. + + + + + + ); +}; + +export default NotFoundPage; + +const Img = styled.img` + object-fit: cover; +`; + +const NotfoundWrapper = styled.div` + display: flex; + position: relative; + flex-direction: column; + justify-content: start; + align-items: center; + min-height: calc(100vh - ${GlobalSize.header}); + width: ${GlobalSize.width}; + margin: 0px -16px; + padding: 0px 16px; + padding-top: 40px; + padding-bottom: 28px; + background-color: ${color.backgroundAlternative}; + ${media.mobile} { + width: 100vw; + } +`; + +const ButtonContainer = styled.div` + position: absolute; + bottom: 1.75rem; + padding: 1rem 0.75rem; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +`; diff --git a/src/pages/index.ts b/src/pages/index.ts index bc2a706..2465935 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -11,3 +11,4 @@ export * from './redirect/StudentVerificationServerRedirect'; export * from './PaymentsCheckout'; export * from './PaymentsFail'; export * from './PaymentsSuccess'; +export * from './NotFound'; diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 60fff45..dccc10c 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -4,7 +4,7 @@ import RoutePath from '@/routes/routePath'; import { RouterProvider, createBrowserRouter } from 'react-router-dom'; import Layout from '@/components/layout/Layout'; import AuthAccessGuard from '@/components/auth/guard/AuthAccessGuard'; -import { Text } from '@/components/common/Wrapper'; +import NotFoundPage from '@/pages/NotFound'; import { AuthServerRedirectNavigate, StudentVerificationServerRedirect, @@ -148,8 +148,10 @@ const router = sentryCreateBrowserRouter([ path: RoutePath.PaymentsSuccess, element: }, - // Todo: 404 Not found page - { path: '*', element: not found page } + { + path: '*', + element: + } ] } ]); diff --git a/src/utils/sentry.ts b/src/utils/sentry.ts index 8eea869..c71bf51 100644 --- a/src/utils/sentry.ts +++ b/src/utils/sentry.ts @@ -12,7 +12,6 @@ const setSentry = () => { function initSentry() { if (process.env.NODE_ENV === 'development') return; - console.log(`VERCEL ENV : ${process.env.VERCEL_ENV}`); Sentry.init({ environment: process.env.VERCEL_ENV, dsn: SENTRY_DSN_KEY, diff --git a/vite.config.ts.timestamp-1724312798440-8bfe4df2623d3.mjs b/vite.config.ts.timestamp-1724312798440-8bfe4df2623d3.mjs new file mode 100644 index 0000000..3c3b8c1 --- /dev/null +++ b/vite.config.ts.timestamp-1724312798440-8bfe4df2623d3.mjs @@ -0,0 +1,27 @@ +// vite.config.ts +import { defineConfig } from "file:///Users/eugene/github/gdsc-client/node_modules/.pnpm/vite@5.1.4_sass@1.71.1/node_modules/vite/dist/node/index.js"; +import react from "file:///Users/eugene/github/gdsc-client/node_modules/.pnpm/@vitejs+plugin-react-swc@3.6.0_vite@5.1.4/node_modules/@vitejs/plugin-react-swc/index.mjs"; +import tsconfigPaths from "file:///Users/eugene/github/gdsc-client/node_modules/.pnpm/vite-tsconfig-paths@4.3.1_typescript@5.3.3_vite@5.1.4/node_modules/vite-tsconfig-paths/dist/index.mjs"; +import { sentryVitePlugin } from "file:///Users/eugene/github/gdsc-client/node_modules/.pnpm/@sentry+vite-plugin@2.21.1/node_modules/@sentry/vite-plugin/dist/esm/index.mjs"; +var vite_config_default = defineConfig({ + plugins: [ + react(), + tsconfigPaths(), + sentryVitePlugin({ + org: process.env.SENTRY_ORG_NAME, + project: process.env.SENTRY_PROJECT_NAME, + authToken: process.env.SENTRY_AUTH_TOKEN, + sourcemaps: { + assets: "./dist/**", + filesToDeleteAfterUpload: "**/*.map" + } + }) + ], + build: { + sourcemap: true + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvVXNlcnMvZXVnZW5lL2dpdGh1Yi9nZHNjLWNsaWVudFwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL2V1Z2VuZS9naXRodWIvZ2RzYy1jbGllbnQvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL1VzZXJzL2V1Z2VuZS9naXRodWIvZ2RzYy1jbGllbnQvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJztcbmltcG9ydCByZWFjdCBmcm9tICdAdml0ZWpzL3BsdWdpbi1yZWFjdC1zd2MnO1xuaW1wb3J0IHRzY29uZmlnUGF0aHMgZnJvbSAndml0ZS10c2NvbmZpZy1wYXRocyc7XG5cbmltcG9ydCB7IHNlbnRyeVZpdGVQbHVnaW4gfSBmcm9tICdAc2VudHJ5L3ZpdGUtcGx1Z2luJztcblxuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHBsdWdpbnM6IFtcbiAgICByZWFjdCgpLFxuICAgIHRzY29uZmlnUGF0aHMoKSxcbiAgICBzZW50cnlWaXRlUGx1Z2luKHtcbiAgICAgIG9yZzogcHJvY2Vzcy5lbnYuU0VOVFJZX09SR19OQU1FLFxuICAgICAgcHJvamVjdDogcHJvY2Vzcy5lbnYuU0VOVFJZX1BST0pFQ1RfTkFNRSxcbiAgICAgIGF1dGhUb2tlbjogcHJvY2Vzcy5lbnYuU0VOVFJZX0FVVEhfVE9LRU4sXG4gICAgICBzb3VyY2VtYXBzOiB7XG4gICAgICAgIGFzc2V0czogJy4vZGlzdC8qKicsXG4gICAgICAgIGZpbGVzVG9EZWxldGVBZnRlclVwbG9hZDogJyoqLyoubWFwJ1xuICAgICAgfVxuICAgIH0pXG4gIF0sXG4gIGJ1aWxkOiB7XG4gICAgc291cmNlbWFwOiB0cnVlXG4gIH1cbn0pO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUFrUixTQUFTLG9CQUFvQjtBQUMvUyxPQUFPLFdBQVc7QUFDbEIsT0FBTyxtQkFBbUI7QUFFMUIsU0FBUyx3QkFBd0I7QUFHakMsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsU0FBUztBQUFBLElBQ1AsTUFBTTtBQUFBLElBQ04sY0FBYztBQUFBLElBQ2QsaUJBQWlCO0FBQUEsTUFDZixLQUFLLFFBQVEsSUFBSTtBQUFBLE1BQ2pCLFNBQVMsUUFBUSxJQUFJO0FBQUEsTUFDckIsV0FBVyxRQUFRLElBQUk7QUFBQSxNQUN2QixZQUFZO0FBQUEsUUFDVixRQUFRO0FBQUEsUUFDUiwwQkFBMEI7QUFBQSxNQUM1QjtBQUFBLElBQ0YsQ0FBQztBQUFBLEVBQ0g7QUFBQSxFQUNBLE9BQU87QUFBQSxJQUNMLFdBQVc7QUFBQSxFQUNiO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K