diff --git a/apps/client/src/app/App.tsx b/apps/client/src/app/App.tsx
index 6be0a6f..a921e82 100644
--- a/apps/client/src/app/App.tsx
+++ b/apps/client/src/app/App.tsx
@@ -1,9 +1,10 @@
+import { ErrorPage, WorkspaceErrorPage } from '@/pages';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
-import { ToasterWithMax } from '@/shared/ui';
-import { ErrorPage } from '@/pages/ErrorPage/ErrorPage';
-import { lazy, Suspense } from 'react';
-import { Loading } from '@/shared/ui';
+import { Suspense, lazy } from 'react';
+
import { Helmet } from 'react-helmet-async';
+import { Loading } from '@/shared/ui';
+import { ToasterWithMax } from '@/shared/ui';
// lazy 로딩
const HomePage = lazy(() =>
@@ -47,12 +48,13 @@ const router = createBrowserRouter([
BooLock - 작업 공간
+
}>
>
),
- errorElement: ,
+ errorElement: ,
},
{
path: '*',
diff --git a/apps/client/src/pages/HomePage/HomePage.tsx b/apps/client/src/pages/HomePage/HomePage.tsx
index abe0c37..2f7a462 100644
--- a/apps/client/src/pages/HomePage/HomePage.tsx
+++ b/apps/client/src/pages/HomePage/HomePage.tsx
@@ -1,4 +1,4 @@
-import { Banner, HomeHeader, WorkspaceContainer, WorkspaceModal } from '@/widgets';
+import { Banner, HomeHeader, WorkspaceModal, WorkspaceSection } from '@/widgets';
import { useClassBlockStore, useLoadingStore, useWorkspaceStore } from '@/shared/store';
import { Loading } from '@/shared/ui';
@@ -26,7 +26,7 @@ export const HomePage = () => {
-
+
>
diff --git a/apps/client/src/pages/WorkspaceErrorPage/WorkspaceErrorPage.tsx b/apps/client/src/pages/WorkspaceErrorPage/WorkspaceErrorPage.tsx
new file mode 100644
index 0000000..eb99bf2
--- /dev/null
+++ b/apps/client/src/pages/WorkspaceErrorPage/WorkspaceErrorPage.tsx
@@ -0,0 +1,14 @@
+import { ErrorPage, NotFound } from '@/pages';
+
+import toast from 'react-hot-toast';
+import { useRouteError } from 'react-router-dom';
+
+export const WorkspaceErrorPage = () => {
+ const error: any = useRouteError();
+ const statusCode = error?.response?.statusCode || error?.status;
+ if (statusCode === 404) {
+ toast.error('워크스페이스 정보 불러오기 실패');
+ return ;
+ }
+ return ;
+};
diff --git a/apps/client/src/pages/Workspacepage/WorkspacePage.tsx b/apps/client/src/pages/Workspacepage/WorkspacePage.tsx
index 32f7bb1..4078b58 100644
--- a/apps/client/src/pages/Workspacepage/WorkspacePage.tsx
+++ b/apps/client/src/pages/Workspacepage/WorkspacePage.tsx
@@ -1,10 +1,9 @@
-import { ImageTagModal, CoachMark, WorkspaceContent, WorkspacePageHeader } from '@/widgets';
+import { CoachMark, ImageTagModal, WorkspaceContent, WorkspacePageHeader } from '@/widgets';
+import { useEffect, useLayoutEffect } from 'react';
import { useGetWorkspace, usePreventLeaveWorkspacePage } from '@/shared/hooks';
-import { Loading } from '@/shared/ui';
-import { NotFound } from '@/pages/NotFound/NotFound';
-import { useParams } from 'react-router-dom';
-import { useLayoutEffect, useEffect } from 'react';
+
import { useCoachMarkStore } from '@/shared/store/useCoachMarkStore';
+import { useParams } from 'react-router-dom';
/**
*
@@ -13,7 +12,7 @@ import { useCoachMarkStore } from '@/shared/store/useCoachMarkStore';
*/
export const WorkspacePage = () => {
const { workspaceId } = useParams();
- const { isPending, isError } = useGetWorkspace(workspaceId as string);
+ useGetWorkspace(workspaceId as string);
usePreventLeaveWorkspacePage();
const { currentStep, isCoachMarkOpen, openCoachMark } = useCoachMarkStore();
const toolboxDiv = document.querySelector('.blocklyToolboxDiv');
@@ -36,14 +35,9 @@ export const WorkspacePage = () => {
}
}, [currentStep, toolboxDiv]);
- if (isError) {
- return ;
- }
-
return (
<>
- {isPending && }
{isCoachMarkOpen && }
diff --git a/apps/client/src/pages/index.ts b/apps/client/src/pages/index.ts
index 90faf1f..a4d2149 100644
--- a/apps/client/src/pages/index.ts
+++ b/apps/client/src/pages/index.ts
@@ -1,3 +1,5 @@
export { HomePage } from './HomePage/HomePage';
export { NotFound } from './NotFound/NotFound';
export { WorkspacePage } from './Workspacepage/WorkspacePage';
+export { ErrorPage } from './ErrorPage/ErrorPage';
+export { WorkspaceErrorPage } from './WorkspaceErrorPage/WorkspaceErrorPage';
diff --git a/apps/client/src/shared/hooks/queries/useGetWorkspace.ts b/apps/client/src/shared/hooks/queries/useGetWorkspace.ts
index 23cb790..83e85e7 100644
--- a/apps/client/src/shared/hooks/queries/useGetWorkspace.ts
+++ b/apps/client/src/shared/hooks/queries/useGetWorkspace.ts
@@ -3,16 +3,15 @@ import { createUserId, getUserId, removeCssClassNamePrefix } from '@/shared/util
import {
useClassBlockStore,
useCssPropsStore,
+ useImageModalStore,
useResetCssStore,
useWorkspaceChangeStatusStore,
useWorkspaceStore,
- useImageModalStore,
} from '@/shared/store';
import { WorkspaceApi } from '@/shared/api';
-import toast from 'react-hot-toast';
import { useEffect } from 'react';
-import { useQuery } from '@tanstack/react-query';
+import { useSuspenseQuery } from '@tanstack/react-query';
import { workspaceKeys } from '@/shared/hooks';
export const useGetWorkspace = (workspaceId: string) => {
@@ -24,29 +23,19 @@ export const useGetWorkspace = (workspaceId: string) => {
const { resetChangedStatusState } = useWorkspaceChangeStatusStore();
const { setIsResetCssChecked } = useResetCssStore();
const { setInitialImageMap, setInitialImageList } = useImageModalStore();
- const { data, isPending, isError } = useQuery({
+ const { data, isPending, isError } = useSuspenseQuery({
queryKey: workspaceKeys.detail(workspaceId),
queryFn: () => {
+ resetChangedStatusState();
return workspaceApi.getWorkspace(userId, workspaceId);
},
});
useEffect(() => {
- resetChangedStatusState();
- }, []);
-
- useEffect(() => {
- if (isError) {
- toast.error('워크스페이스 정보 불러오기 실패');
- return;
- }
- if (!data) {
+ if (!isError || !data || !data.workspaceDto) {
return;
}
- if (!data.workspaceDto) {
- return;
- }
setName(data.workspaceDto.name);
Object.keys(data.workspaceDto.totalCssPropertyObj).forEach((className) => {
createCssClassBlock(className);
diff --git a/apps/client/src/shared/hooks/queries/useGetWorkspaceList.ts b/apps/client/src/shared/hooks/queries/useGetWorkspaceList.ts
index b8f5539..891de16 100644
--- a/apps/client/src/shared/hooks/queries/useGetWorkspaceList.ts
+++ b/apps/client/src/shared/hooks/queries/useGetWorkspaceList.ts
@@ -1,6 +1,7 @@
-import { WorkspaceApi } from '@/shared/api';
import { createUserId, getUserId } from '@/shared/utils';
-import { useInfiniteQuery } from '@tanstack/react-query';
+
+import { WorkspaceApi } from '@/shared/api';
+import { useSuspenseInfiniteQuery } from '@tanstack/react-query';
import { workspaceKeys } from '@/shared/hooks';
export const useGetWorkspaceList = () => {
const workspaceApi = WorkspaceApi();
@@ -12,7 +13,7 @@ export const useGetWorkspaceList = () => {
isFetchingNextPage,
isError,
data: workspaceList,
- } = useInfiniteQuery({
+ } = useSuspenseInfiniteQuery({
queryKey: workspaceKeys.list(),
queryFn: async ({ pageParam }) => {
const isNewUser = !getUserId();
diff --git a/apps/client/src/widgets/home/WorkspaceContainer/WorkspaceContainer.stories.tsx b/apps/client/src/widgets/home/WorkspaceContainer/WorkspaceContainer.stories.tsx
index 7b22b86..f2b36a3 100644
--- a/apps/client/src/widgets/home/WorkspaceContainer/WorkspaceContainer.stories.tsx
+++ b/apps/client/src/widgets/home/WorkspaceContainer/WorkspaceContainer.stories.tsx
@@ -6,7 +6,7 @@ const meta: Meta = {
title: 'widgets/home/WorkspaceContainer',
component: WorkspaceContainer,
parameters: {
- layout: 'fullscreen',
+ layout: 'centered',
},
tags: ['autodocs'],
};
diff --git a/apps/client/src/widgets/home/WorkspaceContainer/WorkspaceContainer.tsx b/apps/client/src/widgets/home/WorkspaceContainer/WorkspaceContainer.tsx
index b7d5857..8da8a85 100644
--- a/apps/client/src/widgets/home/WorkspaceContainer/WorkspaceContainer.tsx
+++ b/apps/client/src/widgets/home/WorkspaceContainer/WorkspaceContainer.tsx
@@ -1,17 +1,16 @@
-import { EmptyWorkspace, WorkspaceGrid, WorkspaceHeader, WorkspaceList } from '@/widgets';
+import { EmptyWorkspace, WorkspaceGrid, WorkspaceList } from '@/widgets';
import { useGetWorkspaceList, useInfiniteScroll, useVirtualScroll } from '@/shared/hooks';
import { SkeletonWorkspaceList } from '@/shared/ui';
import { TWorkspace } from '@/shared/types';
-import { WorkspaceLoadError } from '@/entities';
/**
*
* @description
- * 워크스페이스 헤더와 그리드를 감싸는 컨테이너 컴포넌트
+ * 워크스페이스 리스트를 렌더링하는 컨테이너 컴포넌트
*/
export const WorkspaceContainer = () => {
- const { hasNextPage, fetchNextPage, isPending, isFetchingNextPage, isError, workspaceList } =
+ const { hasNextPage, fetchNextPage, isPending, isFetchingNextPage, workspaceList } =
useGetWorkspaceList();
const { renderedData, offsetY, totalHeight } = useVirtualScroll({
@@ -33,17 +32,8 @@ export const WorkspaceContainer = () => {
const nextFetchTargetRef = useInfiniteScroll({ intersectionCallback: fetchCallback });
return (
-
-
- {isPending && (
-
-
-
- )}
- {isError ? (
-
- ) : (
- workspaceList &&
+ <>
+ {workspaceList &&
(workspaceList.length === 0 ? (
) : (
@@ -57,11 +47,10 @@ export const WorkspaceContainer = () => {
{isFetchingNextPage && }
- ))
- )}
+ ))}
{!isPending && !isFetchingNextPage && hasNextPage && (
)}
-
+ >
);
};
diff --git a/apps/client/src/widgets/home/WorkspaceSection/WorkspaceSection.stories.tsx b/apps/client/src/widgets/home/WorkspaceSection/WorkspaceSection.stories.tsx
new file mode 100644
index 0000000..0ab8538
--- /dev/null
+++ b/apps/client/src/widgets/home/WorkspaceSection/WorkspaceSection.stories.tsx
@@ -0,0 +1,22 @@
+import { Meta, StoryObj } from '@storybook/react';
+
+import { WorkspaceSection } from './WorkspaceSection';
+
+const meta: Meta = {
+ title: 'widgets/home/WorkspaceSection',
+ component: WorkspaceSection,
+ parameters: {
+ layout: 'fullscreen',
+ },
+ tags: ['autodocs'],
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ // propsname: value,
+ },
+};
diff --git a/apps/client/src/widgets/home/WorkspaceSection/WorkspaceSection.tsx b/apps/client/src/widgets/home/WorkspaceSection/WorkspaceSection.tsx
new file mode 100644
index 0000000..7f1184d
--- /dev/null
+++ b/apps/client/src/widgets/home/WorkspaceSection/WorkspaceSection.tsx
@@ -0,0 +1,20 @@
+import { WorkspaceContainer, WorkspaceHeader } from '@/widgets';
+
+import { SkeletonWorkspaceList } from '@/shared/ui';
+import { Suspense } from 'react';
+
+/**
+ *
+ * @description
+ * 워크스페이스 헤더와 컨테이너를 합친 섹션 컴포넌트
+ */
+export const WorkspaceSection = () => {
+ return (
+
+ );
+};
diff --git a/apps/client/src/widgets/index.ts b/apps/client/src/widgets/index.ts
index 68e0015..b4ddcd0 100644
--- a/apps/client/src/widgets/index.ts
+++ b/apps/client/src/widgets/index.ts
@@ -4,8 +4,9 @@ export { WorkspaceList } from './home/WorkspaceList/WorkspaceList';
export { WorkspaceHeader } from './home/WorkspaceHeader/WorkspaceHeader';
export { EmptyWorkspace } from './home/EmptyWorkspace/EmptyWorkspace';
export { WorkspaceGrid } from './home/WorkspaceGrid/WorkspaceGrid';
-export { WorkspaceContainer } from './home/WorkspaceContainer/WorkspaceContainer';
+export { WorkspaceSection } from './home/WorkspaceSection/WorkspaceSection';
export { WorkspaceModal } from './home/WorkspaceModal/WorkspaceModal';
+export { WorkspaceContainer } from './home/WorkspaceContainer/WorkspaceContainer';
export { PreviewBox } from './workspace/PreviewBox/PreviewBox';
export { CoachMark } from './workspace/CoachMark/CoachMark';
diff --git a/apps/server/src/middlewares/errorMiddleware.ts b/apps/server/src/middlewares/errorMiddleware.ts
index a5e8c8b..aaedc57 100644
--- a/apps/server/src/middlewares/errorMiddleware.ts
+++ b/apps/server/src/middlewares/errorMiddleware.ts
@@ -1,5 +1,12 @@
-import { Request, Response, NextFunction } from 'express';
-import { CustomError } from '../utils/customError';
+import {
+ BadRequestError,
+ CustomError,
+ ForbiddenError,
+ UnauthorizedError,
+} from '../utils/customError';
+import { NextFunction, Request, Response } from 'express';
+
+import { NotFound } from '@aws-sdk/client-s3';
import { errorStatus } from '../utils/constants';
// eslint-disable-next-line no-unused-vars
@@ -19,4 +26,16 @@ const errorHandlers: { [key: string]: (err: any, res: Response) => void } = {
CustomError: (err: CustomError, res: Response) => {
res.status(err.statusCode).json({ message: err.message });
},
+ NotFoundError: (err: NotFound, res: Response) => {
+ res.status(errorStatus.HTTP_404_NOT_FOUND).json({ message: err.message });
+ },
+ BadRequestError: (err: BadRequestError, res: Response) => {
+ res.status(errorStatus.HTTP_400_BAD_REQUEST).json({ message: err.message });
+ },
+ UnauthorizedError: (err: UnauthorizedError, res: Response) => {
+ res.status(errorStatus.HTTP_401_UNAUTHORIZED).json({ message: err.message });
+ },
+ ForbiddenError: (err: ForbiddenError, res: Response) => {
+ res.status(errorStatus.HTTP_403_FORBIDDEN).json({ message: err.message });
+ },
};
diff --git a/apps/server/src/utils/customError.ts b/apps/server/src/utils/customError.ts
index 8d98579..0cb6956 100644
--- a/apps/server/src/utils/customError.ts
+++ b/apps/server/src/utils/customError.ts
@@ -10,14 +10,14 @@ export class CustomError extends Error {
}
export class NotFoundError extends CustomError {
- constructor(message = 'Bad request') {
- super(message, errorStatus.HTTP_400_BAD_REQUEST);
+ constructor(message = 'Resource not found') {
+ super(message, errorStatus.HTTP_404_NOT_FOUND);
}
}
export class BadRequestError extends CustomError {
- constructor(message = 'Resource not found') {
- super(message, errorStatus.HTTP_404_NOT_FOUND);
+ constructor(message = 'Bad request') {
+ super(message, errorStatus.HTTP_400_BAD_REQUEST);
}
}