From 108e7e3b1c83daaa71af853c93b9d1721553a4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B7=9C=ED=9A=8C?= <48755156+KimKyuHoi@users.noreply.github.com> Date: Mon, 5 Aug 2024 21:20:57 +0900 Subject: [PATCH] Implement navigate to 404 error page when accessing non-existent URL in workspaceSlug (#244) * Add 404 NotFound Page * Add query error exception handling condition * Navigate to page 404 according to error conditions * Fix to Specify specific error code * Add error and loading conditions * Move to Global Logic of axios Error status * Remove Logic of Axios Error * Comment to change it to navigate * Move File hooks to utils * Fix: CustomNavigate * Revert "Fix: CustomNavigate" This reverts commit 3a48e1e9798d406295e24553cc5fe622fcca2201. * Fix customNavigate to window.location.href * Remove customNavigate * Rebase: Code Conflict --- frontend/src/App.tsx | 11 ++++- frontend/src/pages/error/index.tsx | 42 +++++++++++++++++++ frontend/src/pages/workspace/Index.tsx | 13 +++++- .../src/pages/workspace/document/Index.tsx | 20 +++++++-- frontend/src/routes.tsx | 6 +++ frontend/src/utils/axios.default.ts | 9 ++++ 6 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 frontend/src/pages/error/index.tsx create mode 100644 frontend/src/utils/axios.default.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7f6727ee..c8e6c966 100755 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -22,6 +22,7 @@ import AuthProvider from "./providers/AuthProvider"; import { useErrorHandler } from "./hooks/useErrorHandler"; import * as Sentry from "@sentry/react"; import { useGetSettingsQuery } from "./hooks/api/settings"; +import { isAxios404Error, isAxios500Error } from "./utils/axios.default"; if (import.meta.env.PROD) { Sentry.init({ @@ -76,7 +77,15 @@ function App() { const queryClient = useMemo(() => { return new QueryClient({ queryCache: new QueryCache({ - onError: handleError, + onError: (error) => { + if (isAxios404Error(error)) { + window.location.href = "/404"; + } else if (isAxios500Error(error)) { + window.location.href = "/404"; + } else { + handleError(error); + } + }, }), defaultOptions: { mutations: { diff --git a/frontend/src/pages/error/index.tsx b/frontend/src/pages/error/index.tsx new file mode 100644 index 00000000..defc3e72 --- /dev/null +++ b/frontend/src/pages/error/index.tsx @@ -0,0 +1,42 @@ +import { Box, Typography, Button } from "@mui/material"; +import { useNavigate } from "react-router-dom"; + +const NotFound = () => { + const navigate = useNavigate(); + + const handleGoHome = () => { + navigate("/"); + }; + + return ( + + + 404 + + + Page Not Found + + + The page you are looking for does not exist. + + + + ); +}; + +export default NotFound; diff --git a/frontend/src/pages/workspace/Index.tsx b/frontend/src/pages/workspace/Index.tsx index 62d9d8de..69e2b015 100644 --- a/frontend/src/pages/workspace/Index.tsx +++ b/frontend/src/pages/workspace/Index.tsx @@ -4,7 +4,7 @@ import { useGetWorkspaceDocumentListQuery, } from "../../hooks/api/workspaceDocument"; import { useGetWorkspaceQuery } from "../../hooks/api/workspace"; -import { Box, Button, CircularProgress, Grid, Stack, Typography } from "@mui/material"; +import { Backdrop, Box, Button, CircularProgress, Grid, Stack, Typography } from "@mui/material"; import DocumentCard from "../../components/cards/DocumentCard"; import { useMemo, useState } from "react"; import { Document } from "../../hooks/api/types/document.d"; @@ -15,7 +15,8 @@ import AddIcon from "@mui/icons-material/Add"; function WorkspaceIndex() { const params = useParams(); const navigate = useNavigate(); - const { data: workspace } = useGetWorkspaceQuery(params.workspaceSlug); + const { data: workspace, isLoading } = useGetWorkspaceQuery(params.workspaceSlug); + const { data: documentPageList, fetchNextPage, @@ -31,6 +32,14 @@ function WorkspaceIndex() { ); }, [documentPageList?.pages]); + if (isLoading) { + return ( + + + + ); + } + const handleCreateDocumentModalOpen = () => { setCreateDocumentModalOpen((prev) => !prev); }; diff --git a/frontend/src/pages/workspace/document/Index.tsx b/frontend/src/pages/workspace/document/Index.tsx index 6ee3ca29..2a3fa823 100644 --- a/frontend/src/pages/workspace/document/Index.tsx +++ b/frontend/src/pages/workspace/document/Index.tsx @@ -1,7 +1,7 @@ import { useEffect } from "react"; import { setClient, setDoc } from "../../../store/editorSlice"; import { useDispatch, useSelector } from "react-redux"; -import { Box } from "@mui/material"; +import { Backdrop, Box, CircularProgress } from "@mui/material"; import { useParams } from "react-router-dom"; import { selectUser } from "../../../store/userSlice"; import { useGetDocumentQuery } from "../../../hooks/api/workspaceDocument"; @@ -14,10 +14,16 @@ import { selectSetting } from "../../../store/settingSlice"; function DocumentIndex() { const dispatch = useDispatch(); const params = useParams(); + const userStore = useSelector(selectUser); const settingStore = useSelector(selectSetting); - const { data: workspace } = useGetWorkspaceQuery(params.workspaceSlug); - const { data: document } = useGetDocumentQuery(workspace?.id, params.documentId); + const { data: workspace, isLoading: isWorkspaceLoading } = useGetWorkspaceQuery( + params.workspaceSlug + ); + const { data: document, isLoading: isDocumentLoading } = useGetDocumentQuery( + workspace?.id, + params.documentId + ); const { doc, client } = useYorkieDocument(document?.yorkieDocumentId, userStore.data?.nickname); useEffect(() => { @@ -32,6 +38,14 @@ function DocumentIndex() { }; }, [dispatch, client, doc]); + if (isDocumentLoading || isWorkspaceLoading) { + return ( + + + + ); + } + return ( diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index ff607bce..79dd1d85 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -11,6 +11,7 @@ import DocumentIndex from "./pages/workspace/document/Index"; import DocumentShareIndex from "./pages/workspace/document/share/Index"; import JoinIndex from "./pages/workspace/join/Index"; import MemberIndex from "./pages/workspace/member/Index"; +import NotFound from "./pages/error"; import ProfileIndex from "./pages/settings/profile/Index"; import SettingLayout from "./components/layouts/SettingLayout"; @@ -85,6 +86,11 @@ const codePairRoutes: Array = [ accessType: AccessType.PRIVATE, element: , }, + { + path: "/404", + accessType: AccessType.PUBLIC, + element: , + }, { path: "settings/profile", accessType: AccessType.PRIVATE, diff --git a/frontend/src/utils/axios.default.ts b/frontend/src/utils/axios.default.ts new file mode 100644 index 00000000..acf4bc74 --- /dev/null +++ b/frontend/src/utils/axios.default.ts @@ -0,0 +1,9 @@ +import { AxiosError } from "axios"; + +export const isAxios404Error = (error: unknown): boolean => { + return error instanceof AxiosError && error.response?.status === 404; +}; + +export const isAxios500Error = (error: unknown): boolean => { + return error instanceof AxiosError && error.response?.status === 500; +};