From 56eb75ec366c629e5392ffabb87fecd00d62387d Mon Sep 17 00:00:00 2001 From: TaeYoon Date: Thu, 29 Sep 2022 13:23:49 +0900 Subject: [PATCH] =?UTF-8?q?[FE]=20issue374:=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EB=B0=8F=20UI=20=EA=B0=9C=EC=84=A0=20(#380)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 스터디장은 스터디를 탈퇴할 수 없도록 수정 스터디장이 자신이 만든 스터디를 클릭했을 때 alert를 띄운다. * fix: 세로로 긴 화면에서 무한 스크롤이 적용되지 않는 버그 수정 * feat: api 타입 수정 * feat: 스터디 상세 페이지 태그 ui 개선 태그가 많아질 경우 페이지를 넘어가는 경우가 발생 -> 페이지를 넘어가지 않도록 수정 * chore: react-router upgrade 라우팅할 때 relative 설정을 사용하기 위해 버전 업데이트 * fix: 스터디방 페이지 라우팅 오류 수정 * refactor: tsc 적용 * refactor: App.tsx 라우팅 수정 * feat: 로그인 버튼에 깃허브 로그인이라 명시 * feat: 링크 미리보기 stale time 적용 * chore: link-tab, review-tab 절대 경로 추가 * fix: 깃 머지 충돌 해결 * refactor: 링크 탭, 리뷰 탭 절대 경로 적용 * fix: url path 오류 수정 - 스터디 방 페이지 인덱스로 접근시 notice page로 리다이렉트 - useEffect를 제거하고 App.tsx 라우팅으로 구현 - initialSelectedTabId + TabId 타입 좁히기 --- frontend/.prettierrc.json | 4 + frontend/.storybook/main.js | 6 +- frontend/package-lock.json | 73 +++++++-------- frontend/package.json | 2 +- frontend/src/App.tsx | 80 ++++++++--------- frontend/src/api/community/index.ts | 90 +++++++++---------- frontend/src/api/link-preview/index.ts | 10 ++- frontend/src/api/notice/index.ts | 49 ++++------ frontend/src/api/review/index.ts | 20 ++--- frontend/src/api/reviews/index.ts | 6 +- frontend/src/api/studies/index.ts | 1 + frontend/src/api/study/index.ts | 2 +- frontend/src/components/flex/Flex.style.tsx | 3 +- frontend/src/components/flex/Flex.tsx | 3 + .../infinite-scroll/InfiniteScroll.tsx | 25 +++--- .../src/components/modal/Modal.stories.tsx | 2 +- .../RouteWithCondition.tsx | 14 +++ frontend/src/constants.ts | 29 +++--- frontend/src/custom-types/index.ts | 22 +++++ frontend/src/layout/header/Header.tsx | 2 +- frontend/src/mocks/my-studies.json | 8 +- .../detail-page/components/head/Head.tsx | 2 +- .../pages/error-page/ErrorPage.stories.tsx | 3 +- frontend/src/pages/index.ts | 1 + frontend/src/pages/main-page/MainPage.tsx | 2 +- .../MyStudyCardListSection.tsx | 10 ++- .../pages/study-room-page/StudyRoomPage.tsx | 10 ++- .../components/side-menu/SideMenu.stories.tsx | 6 +- .../components/side-menu/SideMenu.style.tsx | 2 +- .../components/side-menu/SideMenu.tsx | 18 ++-- .../hooks/useStudyRoomPage.tsx | 39 ++++---- .../community-tab-panel/CommunityTabPanel.tsx | 36 ++++---- .../article-list-item/ArticleListItem.tsx | 2 +- .../components/article-list/ArticleList.tsx | 19 ++-- .../components/article/Article.tsx | 34 +++---- .../components/edit/Edit.tsx | 41 ++++----- .../components/publish/Publish.tsx | 39 ++++---- .../LinkRoomTabPanel.stories.tsx | 2 +- .../link-room-tab-panel/LinkRoomTabPanel.tsx | 12 +-- .../link-edit-form/LinkEditForm.stories.tsx | 4 +- .../components/link-form/LinkForm.stories.tsx | 2 +- .../components/link-item/LinkItem.stories.tsx | 4 +- .../components/link-item/LinkItem.tsx | 8 +- .../link-preview/LinkPreview.stories.tsx | 4 +- .../components/link-preview/LinkPreview.tsx | 2 +- .../UserDescription.stories.tsx | 4 +- .../tabs/notice-tab-panel/NoticeTabPanel.tsx | 43 +++++---- .../article-list-item/ArticleListItem.tsx | 2 +- .../components/article-list/ArticleList.tsx | 21 +++-- .../components/article/Article.tsx | 37 ++++---- .../notice-tab-panel/components/edit/Edit.tsx | 48 +++++----- .../components/publish/Publish.tsx | 49 +++++----- .../tabs/review-tab-panel/ReviewTabPanel.tsx | 12 +-- .../reivew-form/ReviewForm.stories.tsx | 4 +- .../review-comment/ReviewComment.stories.tsx | 4 +- .../review-comment/ReviewComment.tsx | 4 +- frontend/tsconfig.json | 4 +- frontend/webpack/webpack.common.js | 4 +- package-lock.json | 6 -- 59 files changed, 496 insertions(+), 499 deletions(-) create mode 100644 frontend/src/components/route-with-condition/RouteWithCondition.tsx delete mode 100644 package-lock.json diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json index bf0cda478..0c70e6679 100644 --- a/frontend/.prettierrc.json +++ b/frontend/.prettierrc.json @@ -27,6 +27,7 @@ "^@context/(.*)$", "^@layout", "^@layout/(.*)$", + "^@pages", "^@pages/(.*)$", "^@components/(.*)$", "^@main-page/(.*)$", @@ -35,7 +36,10 @@ "^@edit-study-page/(.*)$", "^@my-study-page/(.*)$", "^@study-room-page/(.*)$", + "^@notice-tab/(.*)$", "^@community-tab/(.*)$", + "^@link-tab/(.*)$", + "^@review-tab/(.*)$", "^@error-page/(.*)$", "^@login-redirect-page/(.*)$", "^@webpack-page/(.*)$", diff --git a/frontend/.storybook/main.js b/frontend/.storybook/main.js index 3726b1a0c..e07bdcf09 100644 --- a/frontend/.storybook/main.js +++ b/frontend/.storybook/main.js @@ -28,9 +28,11 @@ module.exports = { '@create-study-page': resolve(__dirname, '../src/pages/create-study-page'), '@edit-study-page': resolve(__dirname, '../src/pages/edit-study-page'), '@my-study-page': resolve(__dirname, '../src/pages/my-study-page'), - '@community-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/community-tab-panel'), - '@notice-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/notice-tab-panel'), '@study-room-page': resolve(__dirname, '../src/pages/study-room-page'), + '@notice-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/notice-tab-panel'), + '@community-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/community-tab-panel'), + '@link-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/link-room-tab-panel'), + '@review-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/review-tab-panel'), '@login-redirect-page': resolve(__dirname, '../src/pages/login-redirect-page'), '@error-page': resolve(__dirname, '../src/pages/error-page'), '@layout': resolve(__dirname, '../src/layout'), diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c56cffb89..e420c75b3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,7 +16,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-query": "^3.39.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.4.1" }, "devDependencies": { "@babel/cli": "^7.18.10", @@ -3117,6 +3117,14 @@ "node": ">= 8" } }, + "node_modules/@remix-run/router": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.1.tgz", + "integrity": "sha512-eBV5rvW4dRFOU1eajN7FmYxjAIVz/mRHgUE9En9mBn6m3mulK3WTR5C3iQhL9MZ14rWAq+xOlEaCkDiW0/heOg==", + "engines": { + "node": ">=14" + } + }, "node_modules/@storybook/addon-actions": { "version": "6.5.10", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.5.10.tgz", @@ -18233,14 +18241,6 @@ "integrity": "sha512-lOhQU7iG3AMcjmb8NIWCa+KwfJw5bY44BoWPtrj5A4iDbSD3ylGf5QcYr0ZyQnhkKQ2GgWNLdF2rfrXtXlF3nQ==", "dev": true }, - "node_modules/history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "dependencies": { - "@babel/runtime": "^7.7.6" - } - }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -23414,23 +23414,29 @@ } }, "node_modules/react-router": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", - "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.1.tgz", + "integrity": "sha512-OJASKp5AykDWFewgWUim1vlLr7yfD4vO/h+bSgcP/ix8Md+LMHuAjovA74MQfsfhQJGGN1nHRhwS5qQQbbBt3A==", "dependencies": { - "history": "^5.2.0" + "@remix-run/router": "1.0.1" + }, + "engines": { + "node": ">=14" }, "peerDependencies": { "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", - "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.1.tgz", + "integrity": "sha512-MY7NJCrGNVJtGp8ODMOBHu20UaIkmwD2V3YsAOUQoCXFk7Ppdwf55RdcGyrSj+ycSL9Uiwrb3gTLYSnzcRoXww==", "dependencies": { - "history": "^5.2.0", - "react-router": "6.3.0" + "@remix-run/router": "1.0.1", + "react-router": "6.4.1" + }, + "engines": { + "node": ">=14" }, "peerDependencies": { "react": ">=16.8", @@ -30584,6 +30590,11 @@ } } }, + "@remix-run/router": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.1.tgz", + "integrity": "sha512-eBV5rvW4dRFOU1eajN7FmYxjAIVz/mRHgUE9En9mBn6m3mulK3WTR5C3iQhL9MZ14rWAq+xOlEaCkDiW0/heOg==" + }, "@storybook/addon-actions": { "version": "6.5.10", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.5.10.tgz", @@ -42161,14 +42172,6 @@ "integrity": "sha512-lOhQU7iG3AMcjmb8NIWCa+KwfJw5bY44BoWPtrj5A4iDbSD3ylGf5QcYr0ZyQnhkKQ2GgWNLdF2rfrXtXlF3nQ==", "dev": true }, - "history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "requires": { - "@babel/runtime": "^7.7.6" - } - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -46110,20 +46113,20 @@ "dev": true }, "react-router": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", - "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.1.tgz", + "integrity": "sha512-OJASKp5AykDWFewgWUim1vlLr7yfD4vO/h+bSgcP/ix8Md+LMHuAjovA74MQfsfhQJGGN1nHRhwS5qQQbbBt3A==", "requires": { - "history": "^5.2.0" + "@remix-run/router": "1.0.1" } }, "react-router-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", - "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.1.tgz", + "integrity": "sha512-MY7NJCrGNVJtGp8ODMOBHu20UaIkmwD2V3YsAOUQoCXFk7Ppdwf55RdcGyrSj+ycSL9Uiwrb3gTLYSnzcRoXww==", "requires": { - "history": "^5.2.0", - "react-router": "6.3.0" + "@remix-run/router": "1.0.1", + "react-router": "6.4.1" } }, "read-pkg": { diff --git a/frontend/package.json b/frontend/package.json index 8b97c172c..f3675bdab 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -83,7 +83,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-query": "^3.39.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.4.1" }, "msw": { "workerDirectory": "public" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8e0059d2d..c6769ccad 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,11 +7,14 @@ import { useAuth } from '@hooks/useAuth'; import { Footer, Header, Main } from '@layout'; -import EditStudyPage from '@edit-study-page/EditStudyPage'; +import RouteWithCondition from '@components/route-with-condition/RouteWithCondition'; const CreateStudyPage = lazy( () => import(/* webpackChunkName: "create-study-page" */ '@pages/create-study-page/CreateStudyPage'), ); +const EditStudyPage = lazy( + () => import(/* webpackChunkName: "create-study-page" */ '@pages/edit-study-page/EditStudyPage'), +); const DetailPage = lazy(() => import(/* webpackChunkName: "detail-page" */ '@pages/detail-page/DetailPage')); const ErrorPage = lazy(() => import(/* webpackChunkName: "error-page" */ '@pages/error-page/ErrorPage')); const LoginRedirectPage = lazy( @@ -22,6 +25,12 @@ const MyStudyPage = lazy(() => import(/* webpackChunkName: "my-study-page" */ '@ const StudyRoomPage = lazy( () => import(/* webpackChunkName: "study-room-page" */ '@pages/study-room-page/StudyRoomPage'), ); +const NoticeTabPanel = lazy(() => import(/* webpackChunkName: "notice-tab-panel" */ '@notice-tab/NoticeTabPanel')); +const CommunityTabPanel = lazy( + () => import(/* webpackChunkName: "community-tab-panel" */ '@community-tab/CommunityTabPanel'), +); +const LinkRoomTabPanel = lazy(() => import(/* webpackChunkName: "link-tab-panel" */ '@link-tab/LinkRoomTabPanel')); +const ReviewTabPanel = lazy(() => import(/* webpackChunkName: "review-tab-panel" */ '@review-tab/ReviewTabPanel')); const App = () => { const { isLoggedIn } = useAuth(); @@ -34,50 +43,31 @@ const App = () => { } /> } /> - : } - /> - : } - /> - : } - /> - : } - /> - : } - /> - : } - /> - : } - /> - : } - /> - : } - /> - : } - /> - : } - /> + }> + } /> + + }> + } /> + } /> + } /> + }> + {/* TODO: 인덱스 페이지를 따로 두면 좋을 것 같다. */} + } /> + }> + {[PATH.NOTICE_PUBLISH, PATH.NOTICE_ARTICLE(), PATH.NOTICE_EDIT()].map((path, index) => ( + } /> + ))} + + }> + {[PATH.COMMUNITY_PUBLISH, PATH.COMMUNITY_ARTICLE(), PATH.COMMUNITY_EDIT()].map((path, index) => ( + } /> + ))} + + } /> + } /> + } /> + + } /> diff --git a/frontend/src/api/community/index.ts b/frontend/src/api/community/index.ts index 3d8f8758b..5954c8e80 100644 --- a/frontend/src/api/community/index.ts +++ b/frontend/src/api/community/index.ts @@ -1,7 +1,7 @@ -import { AxiosError, AxiosResponse } from 'axios'; +import { type AxiosError, type AxiosResponse } from 'axios'; import { useMutation, useQuery } from 'react-query'; -import type { CommunityArticle } from '@custom-types'; +import type { ArticleId, CommunityArticle, Page, Size, StudyId } from '@custom-types'; import axiosInstance from '@api/axiosInstance'; @@ -14,61 +14,42 @@ export type ApiCommunityArticles = { totalCount: number; }; params: { - studyId: number; - page?: number; - size?: number; + studyId: StudyId; + page?: Page; + size?: Size; }; variables: ApiCommunityArticles['get']['params']; }; - post: { - responseData: CommunityArticle; - params: { - studyId: number; - articleId: number; - }; - variables: ApiCommunityArticles['post']['params']; - }; }; export type ApiCommunityArticle = { get: { responseData: CommunityArticle; params: { - studyId: number; - articleId: number; + studyId: StudyId; + articleId: ArticleId; }; variables: ApiCommunityArticle['get']['params']; }; post: { - responseData: { - studyId: number; - title: string; - content: string; - }; params: { - studyId: number; - }; - body: { - title: string; - content: string; + studyId: StudyId; }; + body: Pick; variables: ApiCommunityArticle['post']['params'] & ApiCommunityArticle['post']['body']; }; put: { params: { - studyId: number; - articleId: number; - }; - body: { - title: string; - content: string; + studyId: StudyId; + articleId: ArticleId; }; + body: ApiCommunityArticle['post']['body']; variables: ApiCommunityArticle['put']['params'] & ApiCommunityArticle['put']['body']; }; delete: { params: { - studyId: number; - articleId: number; + studyId: StudyId; + articleId: ArticleId; }; variables: ApiCommunityArticle['delete']['params']; }; @@ -91,6 +72,15 @@ const getCommunityArticles = async ({ studyId, page = 1, size = 8 }: ApiCommunit return response.data; }; +// articles +export const useGetCommunityArticles = ({ studyId, page }: ApiCommunityArticles['get']['variables']) => { + return useQuery( + ['get-community-articles', studyId, page], + () => getCommunityArticles({ studyId, page }), + ); +}; + +// get const getCommunityArticle = async ({ studyId, articleId }: ApiCommunityArticle['get']['variables']) => { // 서버쪽에서는 page를 0번부터 계산하기 때문에 page - 1을 해줘야 한다 const response = await axiosInstance.get( @@ -99,6 +89,14 @@ const getCommunityArticle = async ({ studyId, articleId }: ApiCommunityArticle[' return response.data; }; +export const useGetCommunityArticle = ({ studyId, articleId }: ApiCommunityArticle['get']['variables']) => { + return useQuery( + ['get-community-article', studyId, articleId], + () => getCommunityArticle({ studyId, articleId }), + ); +}; + +// post const postCommunityArticle = async ({ studyId, title, content }: ApiCommunityArticle['post']['variables']) => { const response = await axiosInstance.post, ApiCommunityArticle['post']['body']>( `/api/studies/${studyId}/community/articles`, @@ -111,6 +109,11 @@ const postCommunityArticle = async ({ studyId, title, content }: ApiCommunityArt return response.data; }; +export const usePostCommunityArticle = () => { + return useMutation(postCommunityArticle); +}; + +// put const putCommunityArticle = async ({ studyId, title, content, articleId }: ApiCommunityArticle['put']['variables']) => { const response = await axiosInstance.put, ApiCommunityArticle['put']['body']>( `/api/studies/${studyId}/community/articles/${articleId}`, @@ -123,6 +126,11 @@ const putCommunityArticle = async ({ studyId, title, content, articleId }: ApiCo return response.data; }; +export const usePutCommunityArticle = () => { + return useMutation(putCommunityArticle); +}; + +// delete const deleteCommunityArticle = async ({ studyId, articleId }: ApiCommunityArticle['delete']['variables']) => { const response = await axiosInstance.delete>( `/api/studies/${studyId}/community/articles/${articleId}`, @@ -131,22 +139,6 @@ const deleteCommunityArticle = async ({ studyId, articleId }: ApiCommunityArticl return response.data; }; -export const useGetCommunityArticles = (studyId: number, page: number) => { - return useQuery(['get-community-articles', studyId, page], () => getCommunityArticles({ studyId, page })); -}; - -export const useGetCommunityArticle = (studyId: number, articleId: number) => { - return useQuery(['get-community-article', studyId, articleId], () => getCommunityArticle({ studyId, articleId })); -}; - -export const usePostCommunityArticle = () => { - return useMutation(postCommunityArticle); -}; - -export const usePutCommunityArticle = () => { - return useMutation(putCommunityArticle); -}; - export const useDeleteCommunityArticle = () => { return useMutation(deleteCommunityArticle); }; diff --git a/frontend/src/api/link-preview/index.ts b/frontend/src/api/link-preview/index.ts index 513ca2b92..4bdea2600 100644 --- a/frontend/src/api/link-preview/index.ts +++ b/frontend/src/api/link-preview/index.ts @@ -10,7 +10,7 @@ export type ApiLinkPreview = { linkUrl: string; }; responseData: { - title: string; + title: string | null; description: string | null; imageUrl: string | null; domainName: string | null; @@ -69,7 +69,11 @@ export const getLinkPreview = async ({ linkUrl }: ApiLinkPreview['get']['variabl }; export const useGetLinkPreview = ({ linkUrl }: ApiLinkPreview['get']['variables']) => { - return useQuery(['link-preview', linkUrl], () => - getLinkPreview({ linkUrl }), + return useQuery( + ['link-preview', linkUrl], + () => getLinkPreview({ linkUrl }), + { + staleTime: 5 * 60 * 1000, // 5 min + }, ); }; diff --git a/frontend/src/api/notice/index.ts b/frontend/src/api/notice/index.ts index f9d74864b..f4edd4635 100644 --- a/frontend/src/api/notice/index.ts +++ b/frontend/src/api/notice/index.ts @@ -1,7 +1,7 @@ import { AxiosError, AxiosResponse } from 'axios'; import { useMutation, useQuery } from 'react-query'; -import type { NoticeArticle } from '@custom-types'; +import type { ArticleId, NoticeArticle, Page, Size, StudyId } from '@custom-types'; import axiosInstance from '@api/axiosInstance'; @@ -14,61 +14,42 @@ export type ApiNoticeArticles = { totalCount: number; }; params: { - studyId: number; - page?: number; - size?: number; + studyId: StudyId; + page?: Page; + size?: Size; }; variables: ApiNoticeArticles['get']['params']; }; - post: { - responseData: NoticeArticle; - params: { - studyId: number; - articleId: number; - }; - variables: ApiNoticeArticles['post']['params']; - }; }; export type ApiNoticeArticle = { get: { responseData: NoticeArticle; params: { - studyId: number; - articleId: number; + studyId: StudyId; + articleId: ArticleId; }; variables: ApiNoticeArticle['get']['params']; }; post: { - responseData: { - studyId: number; - title: string; - content: string; - }; params: { - studyId: number; - }; - body: { - title: string; - content: string; + studyId: StudyId; }; + body: Pick; variables: ApiNoticeArticle['post']['params'] & ApiNoticeArticle['post']['body']; }; put: { params: { - studyId: number; - articleId: number; - }; - body: { - title: string; - content: string; + studyId: StudyId; + articleId: ArticleId; }; + body: ApiNoticeArticle['post']['body']; variables: ApiNoticeArticle['put']['params'] & ApiNoticeArticle['put']['body']; }; delete: { params: { - studyId: number; - articleId: number; + studyId: StudyId; + articleId: ArticleId; }; variables: ApiNoticeArticle['delete']['params']; }; @@ -131,11 +112,11 @@ const deleteNoticeArticle = async ({ studyId, articleId }: ApiNoticeArticle['del return response.data; }; -export const useGetNoticeArticles = (studyId: number, page: number) => { +export const useGetNoticeArticles = ({ studyId, page }: ApiNoticeArticles['get']['variables']) => { return useQuery(['get-notice-articles', studyId, page], () => getNoticeArticles({ studyId, page })); }; -export const useGetNoticeArticle = (studyId: number, articleId: number) => { +export const useGetNoticeArticle = ({ studyId, articleId }: ApiNoticeArticle['get']['variables']) => { return useQuery(['get-notice-article', studyId, articleId], () => getNoticeArticle({ studyId, articleId })); }; diff --git a/frontend/src/api/review/index.ts b/frontend/src/api/review/index.ts index 1f845bfc2..84bebcb1d 100644 --- a/frontend/src/api/review/index.ts +++ b/frontend/src/api/review/index.ts @@ -1,7 +1,7 @@ import type { AxiosError, AxiosResponse } from 'axios'; import { useMutation } from 'react-query'; -import type { ReviewId, StudyId } from '@custom-types'; +import type { ReviewId, StudyId, StudyReview } from '@custom-types'; import axiosInstance from '@api/axiosInstance'; @@ -10,27 +10,23 @@ export type ApiReview = { params: { studyId: StudyId; }; - body: { - content: string; - }; + body: Pick; variables: ApiReview['post']['params'] & ApiReview['post']['body']; }; put: { params: { - studyId: number; - reviewId: number; - }; - body: { - content: string; + studyId: StudyId; + reviewId: ReviewId; }; + body: ApiReview['post']['body']; variables: ApiReview['put']['params'] & ApiReview['put']['body']; }; delete: { - body: { + params: { studyId: StudyId; reviewId: ReviewId; }; - variables: ApiReview['delete']['body']; + variables: ApiReview['delete']['params']; }; }; @@ -65,4 +61,4 @@ export const deleteReview = async ({ studyId, reviewId }: ApiReview['delete']['v return response.data; }; -export const useDeleteReview = () => useMutation(deleteReview); +export const useDeleteReview = () => useMutation(deleteReview); diff --git a/frontend/src/api/reviews/index.ts b/frontend/src/api/reviews/index.ts index dfde0821b..17f5e2ba5 100644 --- a/frontend/src/api/reviews/index.ts +++ b/frontend/src/api/reviews/index.ts @@ -1,7 +1,7 @@ import type { AxiosError } from 'axios'; import { useQuery } from 'react-query'; -import type { StudyReview } from '@custom-types'; +import type { Size, StudyId, StudyReview } from '@custom-types'; import axiosInstance from '@api/axiosInstance'; @@ -10,8 +10,8 @@ export const QK_STUDY_REVIEWS = 'study-reviews'; export type ApiReviews = { get: { params: { - studyId: number; - size?: number; + studyId: StudyId; + size?: Size; }; responseData: { reviews: Array; diff --git a/frontend/src/api/studies/index.ts b/frontend/src/api/studies/index.ts index 4b1dfd88d..a2bf89809 100644 --- a/frontend/src/api/studies/index.ts +++ b/frontend/src/api/studies/index.ts @@ -64,6 +64,7 @@ const getStudiesWithPage = selectedFilters, size: DEFAULT_STUDY_CARD_QUERY_PARAM.SIZE, }); + return { ...data, page: pageParam.page + 1 }; }; diff --git a/frontend/src/api/study/index.ts b/frontend/src/api/study/index.ts index dae81717b..9d7000a74 100644 --- a/frontend/src/api/study/index.ts +++ b/frontend/src/api/study/index.ts @@ -10,7 +10,7 @@ export const QK_STUDY_DETAIL = 'study-detail'; export type ApiStudy = { get: { params: { - studyId: number; + studyId: StudyId; }; responseData: StudyDetail; variables: ApiStudy['get']['params']; diff --git a/frontend/src/components/flex/Flex.style.tsx b/frontend/src/components/flex/Flex.style.tsx index d671648f9..c362caee6 100644 --- a/frontend/src/components/flex/Flex.style.tsx +++ b/frontend/src/components/flex/Flex.style.tsx @@ -6,7 +6,7 @@ import { FlexProps } from './Flex'; type StyledFlexProps = Omit; export const Flex = styled.div` - ${({ width, height, gap, alignItems, justifyContent, direction, rowGap, grow }) => css` + ${({ width, height, gap, alignItems, justifyContent, direction, rowGap, grow, wrap }) => css` display: flex; ${direction && `flex-direction: ${direction}`}; ${width && `width: ${width}`}; @@ -16,5 +16,6 @@ export const Flex = styled.div` ${justifyContent && `justify-content: ${justifyContent}`}; ${rowGap && `row-gap: ${rowGap}`}; ${grow && `flex-grow: 1`}; + ${wrap && `flex-wrap: wrap`}; `} `; diff --git a/frontend/src/components/flex/Flex.tsx b/frontend/src/components/flex/Flex.tsx index 747d2790a..9828d18fe 100644 --- a/frontend/src/components/flex/Flex.tsx +++ b/frontend/src/components/flex/Flex.tsx @@ -12,6 +12,7 @@ export type FlexProps = { justifyContent?: FlexProps['alignItems']; rowGap?: CssLength; grow?: boolean; + wrap?: boolean; }; const Flex: React.FC = ({ @@ -24,6 +25,7 @@ const Flex: React.FC = ({ justifyContent, rowGap, grow, + wrap, }) => ( = ({ justifyContent={justifyContent} rowGap={rowGap} grow={grow} + wrap={wrap} > {children} diff --git a/frontend/src/components/infinite-scroll/InfiniteScroll.tsx b/frontend/src/components/infinite-scroll/InfiniteScroll.tsx index e0b56bac7..dadf9a2af 100644 --- a/frontend/src/components/infinite-scroll/InfiniteScroll.tsx +++ b/frontend/src/components/infinite-scroll/InfiniteScroll.tsx @@ -4,21 +4,21 @@ import type { Noop } from '@custom-types'; type InfiniteScrollProps = { onContentLoad: Noop; - observingCondition: boolean; + isContentLoading: boolean; children: React.ReactNode; }; const InfiniteScroll: React.FC = ({ onContentLoad: handleContentLoad, - observingCondition, + isContentLoading, children, }) => { - const endRef = useRef(null); + const endOfContentRef = useRef(null); const endOfContentObserver = useMemo( () => - new IntersectionObserver(entries => { - if (entries[0].isIntersecting) { + new IntersectionObserver(([entry]) => { + if (entry.isIntersecting) { handleContentLoad(); } }), @@ -26,18 +26,21 @@ const InfiniteScroll: React.FC = ({ ); useEffect(() => { - if (!endRef.current) return; - if (observingCondition) { - endOfContentObserver.observe(endRef.current); + if (!endOfContentRef.current) return; + + if (!isContentLoading) { + // 콘텐트 로딩이 끝나면 타겟을 다시 observe -> 뷰포트 내부에 타겟이 존재하면 콘텐트 추가 로드 + endOfContentObserver.observe(endOfContentRef.current); return; } - endOfContentObserver.unobserve(endRef.current); - }, [observingCondition, endRef.current]); + + return () => endOfContentObserver.disconnect(); + }, [isContentLoading, endOfContentRef.current]); return ( <> {children} -
+
); }; diff --git a/frontend/src/components/modal/Modal.stories.tsx b/frontend/src/components/modal/Modal.stories.tsx index 0fea3eadf..1a3022753 100644 --- a/frontend/src/components/modal/Modal.stories.tsx +++ b/frontend/src/components/modal/Modal.stories.tsx @@ -7,7 +7,7 @@ import { BoxButton } from '@components/button'; import Modal from '@components/modal/Modal'; import type { ModalProps } from '@components/modal/Modal'; -import LinkForm from '@study-room-page/tabs/link-room-tab-panel/components/link-form/LinkForm'; +import LinkForm from '@link-tab/components/link-form/LinkForm'; export default { title: 'Components/Modal', diff --git a/frontend/src/components/route-with-condition/RouteWithCondition.tsx b/frontend/src/components/route-with-condition/RouteWithCondition.tsx new file mode 100644 index 000000000..1e6314f04 --- /dev/null +++ b/frontend/src/components/route-with-condition/RouteWithCondition.tsx @@ -0,0 +1,14 @@ +import { Navigate, Outlet } from 'react-router-dom'; + +import { PATH } from '@constants'; + +export type RouteWithConditionProps = { + routingCondition: boolean; +}; + +const RouteWithCondition: React.FC = ({ routingCondition }) => { + if (!routingCondition) return ; + return ; +}; + +export default RouteWithCondition; diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts index 8c9b16d7f..3de4f0024 100644 --- a/frontend/src/constants.ts +++ b/frontend/src/constants.ts @@ -4,24 +4,23 @@ export const PATH = { CREATE_STUDY: '/study/create', EDIT_STUDY: (studyId: ':studyId' | number = ':studyId') => `/study/edit/${studyId}`, MY_STUDY: '/my/study', - STUDY_ROOM: (studyId: ':studyId' | number = ':studyId') => `/studyroom/${studyId}`, LOGIN: '/login', - REVIEW: (studyId: string | number = ':studyId') => `/studyroom/${studyId}/reviews`, - COMMUNITY: (studyId: ':studyId' | string | number = ':studyId') => `/studyroom/${studyId}/community`, - COMMUNITY_ARTICLE: (studyId: string | number = ':studyId', articleId: string | number = ':articleId') => - `/studyroom/${studyId}/community/article/${articleId}`, - COMMUNITY_PUBLISH: (studyId: string | number = ':studyId') => `/studyroom/${studyId}/community/article/publish`, - COMMUNITY_EDIT: (studyId: string | number = ':studyId', articleId: string | number = ':articleId') => - `/studyroom/${studyId}/community/article/${articleId}/edit`, + STUDY_ROOM: (studyId: ':studyId' | number = ':studyId') => `/studyroom/${studyId}`, - NOTICE: (studyId: ':studyId' | string | number = ':studyId') => `/studyroom/${studyId}/notice`, - NOTICE_ARTICLE: (studyId: string | number = ':studyId', articleId: string | number = ':articleId') => - `/studyroom/${studyId}/notice/article/${articleId}`, - NOTICE_PUBLISH: (studyId: string | number = ':studyId') => `/studyroom/${studyId}/notice/article/publish`, - NOTICE_EDIT: (studyId: string | number = ':studyId', articleId: string | number = ':articleId') => - `/studyroom/${studyId}/notice/article/${articleId}/edit`, -}; + REVIEW: 'reviews', + LINK: 'links', + + NOTICE: 'notice', + NOTICE_PUBLISH: 'article/publish', + NOTICE_ARTICLE: (articleId: ':articleId' | number = ':articleId') => `article/${articleId}`, + NOTICE_EDIT: (articleId: ':articleId' | number = ':articleId') => `article/${articleId}/edit`, + + COMMUNITY: 'community', + COMMUNITY_PUBLISH: 'article/publish', + COMMUNITY_ARTICLE: (articleId: ':articleId' | number = ':articleId') => `article/${articleId}`, + COMMUNITY_EDIT: (articleId: ':articleId' | number = ':articleId') => `article/${articleId}/edit`, +} as const; export const API_ERROR = { EXPIRED_REFRESH_TOKEN: { diff --git a/frontend/src/custom-types/index.ts b/frontend/src/custom-types/index.ts index 4d8e8bfda..96eaf407a 100644 --- a/frontend/src/custom-types/index.ts +++ b/frontend/src/custom-types/index.ts @@ -26,6 +26,7 @@ export type CategoryId = number; export type LinkId = number; export type Page = number; export type Size = number; +export type ArticleId = number; export type SitePage = 'home' | 'studyroom'; @@ -126,3 +127,24 @@ export type NoticeArticle = { }; export type NoticeArticleMode = 'publish' | 'edit'; + +// api +export type GetMethod = { + variables: Params; + responseData: ResponseData; +}; + +export type PostMethod = { + variables: Params & Body; + responseData: ResponseData; +}; + +export type PutMethod = { + variables: Params & Body; + responseData: null; +}; + +export type DeleteMethod = { + variables: Params; + responseData: null; +}; diff --git a/frontend/src/layout/header/Header.tsx b/frontend/src/layout/header/Header.tsx index 61d369137..fb988098e 100644 --- a/frontend/src/layout/header/Header.tsx +++ b/frontend/src/layout/header/Header.tsx @@ -96,7 +96,7 @@ const Header: React.FC = () => { - 로그인 + Github 로그인 )} diff --git a/frontend/src/mocks/my-studies.json b/frontend/src/mocks/my-studies.json index bc254f19b..34a4db530 100644 --- a/frontend/src/mocks/my-studies.json +++ b/frontend/src/mocks/my-studies.json @@ -9,10 +9,10 @@ "startDate": "2022-07-12", "endDate": "2022-08-18", "owner": { - "id": 19749913, - "username": "xRC3N", - "imageUrl": "https://picsum.photos/id/39/200/200", - "profileUrl": "https://github.com/user/jaejae-yoo" + "id": 20, + "username": "tco0427", + "imageUrl": "https://images.unsplash.com/flagged/photo-1570612861542-284f4c12e75f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80", + "profileUrl": "github.com" }, "tags": [ { "id": 1, "name": "JS" }, diff --git a/frontend/src/pages/detail-page/components/head/Head.tsx b/frontend/src/pages/detail-page/components/head/Head.tsx index a45a4151a..2fbc0111f 100644 --- a/frontend/src/pages/detail-page/components/head/Head.tsx +++ b/frontend/src/pages/detail-page/components/head/Head.tsx @@ -48,7 +48,7 @@ const Head: React.FC = ({ 종료일: {(endDate && changeDateSeperator(endDate)) || '없음'} "{excerpt}" - + {tags.map(({ id, name }) => ( #{name} ))} diff --git a/frontend/src/pages/error-page/ErrorPage.stories.tsx b/frontend/src/pages/error-page/ErrorPage.stories.tsx index 20df5a753..27c50ac0b 100644 --- a/frontend/src/pages/error-page/ErrorPage.stories.tsx +++ b/frontend/src/pages/error-page/ErrorPage.stories.tsx @@ -1,6 +1,7 @@ -import { ErrorPage } from '@pages'; import type { Story } from '@storybook/react'; +import { ErrorPage } from '@pages'; + import Wrapper from '@components/wrapper/Wrapper'; export default { diff --git a/frontend/src/pages/index.ts b/frontend/src/pages/index.ts index aab4b0369..92ea1e563 100644 --- a/frontend/src/pages/index.ts +++ b/frontend/src/pages/index.ts @@ -3,5 +3,6 @@ export { default as LoginRedirectPage } from '@login-redirect-page/LoginRedirect export { default as MainPage } from '@main-page/MainPage'; export { default as DetailPage } from '@detail-page/DetailPage'; export { default as CreateStudyPage } from '@create-study-page/CreateStudyPage'; +export { default as EditStudyPage } from '@edit-study-page/EditStudyPage'; export { default as MyStudyPage } from '@my-study-page/MyStudyPage'; export { default as StudyRoomPage } from '@study-room-page/StudyRoomPage'; diff --git a/frontend/src/pages/main-page/MainPage.tsx b/frontend/src/pages/main-page/MainPage.tsx index f79bfe23e..e1ffecc6f 100644 --- a/frontend/src/pages/main-page/MainPage.tsx +++ b/frontend/src/pages/main-page/MainPage.tsx @@ -31,7 +31,7 @@ const MainPage: React.FC = () => { } return ( - + {searchedStudies.map((study, i) => (
  • diff --git a/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.tsx b/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.tsx index 84e446834..53b7ac7f1 100644 --- a/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.tsx +++ b/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.tsx @@ -10,6 +10,8 @@ import type { MyStudy } from '@custom-types'; import { QK_MY_STUDIES } from '@api/my-studies'; import { useDeleteMyStudy } from '@api/my-study'; +import { useUserInfo } from '@hooks/useUserInfo'; + import LinkedButton from '@components/button/linked-button/LinkedButton'; import SectionTitle from '@components/section-title/SectionTitle'; @@ -25,12 +27,18 @@ export type MyStudyCardListSectionProps = { const MyStudyCardListSection: React.FC = ({ sectionTitle, studies, done = false }) => { const queryClient = useQueryClient(); const { mutate } = useDeleteMyStudy(); + const { userInfo } = useUserInfo(); const handleTrashButtonClick = - ({ title, id }: Pick) => + ({ title, id, owner }: Pick) => (e: React.MouseEvent) => { e.stopPropagation(); + if (userInfo.id === owner.id) { + alert('스터디장은 스터디를 탈퇴할 수 없습니다.'); + return; + } + if (!confirm(`정말 '${title}'을(를) 탈퇴하실 건가요? :(`)) return; mutate( diff --git a/frontend/src/pages/study-room-page/StudyRoomPage.tsx b/frontend/src/pages/study-room-page/StudyRoomPage.tsx index bfd635618..c380c7bf0 100644 --- a/frontend/src/pages/study-room-page/StudyRoomPage.tsx +++ b/frontend/src/pages/study-room-page/StudyRoomPage.tsx @@ -1,4 +1,4 @@ -import { Navigate } from 'react-router-dom'; +import { Navigate, Outlet } from 'react-router-dom'; import { PATH } from '@constants'; @@ -11,7 +11,7 @@ import SideMenu from '@study-room-page/components/side-menu/SideMenu'; import useStudyRoomPage from '@study-room-page/hooks/useStudyRoomPage'; const StudyRoomPage: React.FC = () => { - const { tabs, activeTab, userRoleQueryResult, handleTabButtonClick } = useStudyRoomPage(); + const { tabs, activeTabId, userRoleQueryResult, handleTabButtonClick } = useStudyRoomPage(); const { data, isError, isSuccess } = userRoleQueryResult; if (isSuccess && data.role === 'NON_MEMBER') { @@ -27,8 +27,10 @@ const StudyRoomPage: React.FC = () => { return ( - -
    {activeTab.content}
    + +
    + +
    ); diff --git a/frontend/src/pages/study-room-page/components/side-menu/SideMenu.stories.tsx b/frontend/src/pages/study-room-page/components/side-menu/SideMenu.stories.tsx index 744176d46..728c03de3 100644 --- a/frontend/src/pages/study-room-page/components/side-menu/SideMenu.stories.tsx +++ b/frontend/src/pages/study-room-page/components/side-menu/SideMenu.stories.tsx @@ -12,9 +12,9 @@ export default { const Template: Story = () => { const tabs: Tabs = [ - { id: 'notice', name: '공지사항', content: '공지사항입니다.' }, - { id: 'link-room', name: '자료실', content: '자료실입니다.' }, - { id: 'review', name: '후기', content: '후기입니다.' }, + { id: 'notice', name: '공지사항' }, + { id: 'link-room', name: '자료실' }, + { id: 'review', name: '후기' }, ]; const [activeTabId, setActiveTabId] = useState(tabs[0].id); diff --git a/frontend/src/pages/study-room-page/components/side-menu/SideMenu.style.tsx b/frontend/src/pages/study-room-page/components/side-menu/SideMenu.style.tsx index 2ff562f0a..320863a10 100644 --- a/frontend/src/pages/study-room-page/components/side-menu/SideMenu.style.tsx +++ b/frontend/src/pages/study-room-page/components/side-menu/SideMenu.style.tsx @@ -26,7 +26,7 @@ export const Sidebar = styled.nav` export const Bottombar = styled.nav` ${({ theme }) => css` display: flex; - align-items: space-between; + justify-content: space-between; column-gap: 16px; position: fixed; diff --git a/frontend/src/pages/study-room-page/components/side-menu/SideMenu.tsx b/frontend/src/pages/study-room-page/components/side-menu/SideMenu.tsx index e47f374bb..e42cef251 100644 --- a/frontend/src/pages/study-room-page/components/side-menu/SideMenu.tsx +++ b/frontend/src/pages/study-room-page/components/side-menu/SideMenu.tsx @@ -1,3 +1,5 @@ +import { Link } from 'react-router-dom'; + import * as S from '@study-room-page/components/side-menu/SideMenu.style'; import TabButton from '@study-room-page/components/tab-button/TabButton'; import type { TabId, Tabs } from '@study-room-page/hooks/useStudyRoomPage'; @@ -13,16 +15,20 @@ const SideMenu: React.FC = ({ activeTabId, tabs, onTabButtonClick <> {tabs.map(({ id, name }) => ( - - {name} - + + + {name} + + ))} {tabs.map(({ id, name }) => ( - - {name} - + + + {name} + + ))} diff --git a/frontend/src/pages/study-room-page/hooks/useStudyRoomPage.tsx b/frontend/src/pages/study-room-page/hooks/useStudyRoomPage.tsx index 656d7a1ac..e4190c320 100644 --- a/frontend/src/pages/study-room-page/hooks/useStudyRoomPage.tsx +++ b/frontend/src/pages/study-room-page/hooks/useStudyRoomPage.tsx @@ -1,43 +1,42 @@ -import NoticeTabPanel from '@notice-tab/NoticeTabPanel'; import { useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; -import { useGetUserRole } from '@api/member'; - -import CommunityTabPanel from '@study-room-page/tabs/community-tab-panel/CommunityTabPanel'; -import LinkRoomTabPanel from '@study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel'; -import ReviewTabPanel from '@study-room-page/tabs/review-tab-panel/ReviewTabPanel'; - -export type TabId = 'notice' | 'community' | 'link-room' | 'review'; +import { PATH } from '@constants'; -export type Tab = { id: TabId; name: string; content: React.ReactNode }; +import { useGetUserRole } from '@api/member'; +export type TabId = typeof PATH[keyof Pick]; +export type Tab = { id: TabId; name: string }; export type Tabs = Array; const useStudyRoomPage = () => { - const { studyId: _studyId } = useParams() as { studyId: string }; + const location = useLocation(); + + const { studyId: _studyId } = useParams<{ studyId: string }>(); const studyId = Number(_studyId); - const userRoleQueryResult = useGetUserRole({ studyId: Number(studyId) }); + const userRoleQueryResult = useGetUserRole({ studyId }); const tabs: Tabs = [ - { id: 'notice', name: '공지사항', content: }, - { id: 'community', name: '게시판', content: }, - { id: 'link-room', name: '링크 모음', content: }, - { id: 'review', name: '후기', content: }, + { id: PATH.NOTICE, name: '공지사항' }, + { id: PATH.COMMUNITY, name: '게시판' }, + { id: PATH.LINK, name: '링크 모음' }, + { id: PATH.REVIEW, name: '후기' }, ]; + const tabIds = tabs.map(tab => tab.id); - const [activeTabId, setActiveTabId] = useState(tabs[0].id); - - const activeTab = tabs.find(({ id }) => id === activeTabId) as Tab; + const paths = location.pathname.split('/'); + const initialSelectedTabId = tabIds.find(tabId => paths.includes(tabId)) ?? tabs[0].id; + const [activeTabId, setActiveTabId] = useState(initialSelectedTabId); const handleTabButtonClick = (id: TabId) => () => { setActiveTabId(id); }; return { + studyId, tabs, - activeTab, + activeTabId, userRoleQueryResult, handleTabButtonClick, }; diff --git a/frontend/src/pages/study-room-page/tabs/community-tab-panel/CommunityTabPanel.tsx b/frontend/src/pages/study-room-page/tabs/community-tab-panel/CommunityTabPanel.tsx index 897705dd5..c95ea89b7 100644 --- a/frontend/src/pages/study-room-page/tabs/community-tab-panel/CommunityTabPanel.tsx +++ b/frontend/src/pages/study-room-page/tabs/community-tab-panel/CommunityTabPanel.tsx @@ -1,4 +1,4 @@ -import { useNavigate, useParams } from 'react-router-dom'; +import { Link, useLocation, useParams } from 'react-router-dom'; import { PATH } from '@constants'; @@ -14,34 +14,29 @@ import Article from '@community-tab/components/article/Article'; import Edit from '@community-tab/components/edit/Edit'; import Publish from '@community-tab/components/publish/Publish'; -type CommunityTabPanelProps = { - studyId: number; -}; - -const CommunityTabPanel: React.FC = ({ studyId }) => { - const { articleId } = useParams<{ articleId: string }>(); - const navigate = useNavigate(); +const CommunityTabPanel: React.FC = () => { + const location = useLocation(); + const { studyId: _studyId, articleId: _articleId } = useParams<{ studyId: string; articleId: string }>(); + const [studyId, articleId] = [Number(_studyId), Number(_articleId)]; - const lastPath = window.location.pathname.split('/').at(-1); + const lastPath = location.pathname.split('/').at(-1); const isPublishPage = lastPath === 'publish'; const isEditPage = lastPath === 'edit'; const isArticleDetailPage = !!(articleId && !isPublishPage && !isEditPage); const isListPage = !!(!articleId && !isPublishPage && !isEditPage && !isArticleDetailPage); - const handleGoToPublishPageButtonClick = () => { - navigate(`${PATH.COMMUNITY_PUBLISH(studyId)}`); - }; - const renderArticleListPage = () => { return ( <> - - 글쓰기 - + + + 글쓰기 + + - + ); }; @@ -51,14 +46,13 @@ const CommunityTabPanel: React.FC = ({ studyId }) => { return renderArticleListPage(); } if (isArticleDetailPage) { - const numArticleId = Number(articleId); - return
    ; + return
    ; } if (isPublishPage) { - return ; + return ; } if (isEditPage) { - return ; + return ; } }; diff --git a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article-list-item/ArticleListItem.tsx b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article-list-item/ArticleListItem.tsx index f51a7f335..86573e06d 100644 --- a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article-list-item/ArticleListItem.tsx +++ b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article-list-item/ArticleListItem.tsx @@ -8,7 +8,7 @@ import { theme } from '@styles/theme'; import Flex from '@components/flex/Flex'; import UserInfoItem from '@components/user-info-item/UserInfoItem'; -import * as S from '@study-room-page/tabs/community-tab-panel/components/article-list-item/ArticleListItem.style'; +import * as S from '@community-tab/components/article-list-item/ArticleListItem.style'; export type ArticleListItemProps = Pick; diff --git a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article-list/ArticleList.tsx b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article-list/ArticleList.tsx index c91dac7c9..572d290b5 100644 --- a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article-list/ArticleList.tsx +++ b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article-list/ArticleList.tsx @@ -1,22 +1,25 @@ import { useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { PATH } from '@constants'; +import type { StudyId } from '@custom-types'; + import { useGetCommunityArticles } from '@api/community'; import Divider from '@components/divider/Divider'; import Flex from '@components/flex/Flex'; -import ArticleListItem from '@study-room-page/tabs/community-tab-panel/components/article-list-item/ArticleListItem'; - +import ArticleListItem from '@community-tab/components/article-list-item/ArticleListItem'; import Pagination from '@community-tab/components/pagination/Pagination'; -const ArticleList: React.FC = () => { - const { studyId } = useParams<{ studyId: string }>(); - const numStudyId = Number(studyId); +export type ArticleListProps = { + studyId: StudyId; +}; + +const ArticleList: React.FC = ({ studyId }) => { const [page, setPage] = useState(1); - const { isFetching, isSuccess, isError, data } = useGetCommunityArticles(numStudyId, page); + const { isFetching, isSuccess, isError, data } = useGetCommunityArticles({ studyId, page }); if (isFetching) { return
    Loading...
    ; @@ -37,7 +40,7 @@ const ArticleList: React.FC = () => {
      {articles.map(article => (
    • - + diff --git a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article/Article.tsx b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article/Article.tsx index a5e991997..fe4d84103 100644 --- a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article/Article.tsx +++ b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/article/Article.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { PATH } from '@constants'; @@ -9,8 +9,6 @@ import tw from '@utils/tw'; import { useDeleteCommunityArticle, useGetCommunityArticle } from '@api/community'; import { useGetUserInformation } from '@api/member'; -import { useAuth } from '@hooks/useAuth'; - import { BoxButton } from '@components/button'; import ButtonGroup from '@components/button-group/ButtonGroup'; import Divider from '@components/divider/Divider'; @@ -25,15 +23,11 @@ export type ArticleProps = { }; const Article: FC = ({ studyId, articleId }) => { - const { isFetching, isSuccess, isError, data } = useGetCommunityArticle(studyId, articleId); + const { isFetching, isSuccess, isError, data } = useGetCommunityArticle({ studyId, articleId }); const getUserInformationQueryResult = useGetUserInformation(); const { mutateAsync } = useDeleteCommunityArticle(); const navigate = useNavigate(); - const {} = useAuth(); - const handleBackToArticleListButtonClick = () => { - navigate(`${PATH.COMMUNITY(studyId)}`); - }; const handleDeleteArticleButtonClick = () => { mutateAsync( @@ -41,19 +35,15 @@ const Article: FC = ({ studyId, articleId }) => { { onSuccess: () => { alert('성공적으로 삭제했습니다'); - navigate(`${PATH.COMMUNITY(studyId)}`); + navigate(`../${PATH.COMMUNITY}`); }, onError: () => { - alert('알수 없는 에러가 발생했습니다'); + alert('알 수 없는 에러가 발생했습니다'); }, }, ); }; - const handleEditArticleButtonClick = () => { - navigate(`${PATH.COMMUNITY_EDIT(studyId, articleId)}`); - }; - const renderModifierButtons = () => { if (!getUserInformationQueryResult.isSuccess || getUserInformationQueryResult.isError) return; if (!data?.author.username) return; @@ -61,9 +51,11 @@ const Article: FC = ({ studyId, articleId }) => { return ( - - 글수정 - + + + 글수정 + + = ({ studyId, articleId }) => {
  • - - 목록보기 - + + + 목록보기 + + ); } diff --git a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/edit/Edit.tsx b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/edit/Edit.tsx index 69b07025c..876fc6f0f 100644 --- a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/edit/Edit.tsx +++ b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/edit/Edit.tsx @@ -1,7 +1,9 @@ -import { useNavigate, useParams } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { PATH } from '@constants'; +import type { ArticleId, StudyId } from '@custom-types'; + import { useGetCommunityArticle, usePutCommunityArticle } from '@api/community'; import { FormProvider, UseFormSubmitResult, useForm } from '@hooks/useForm'; @@ -15,20 +17,18 @@ import PageTitle from '@components/page-title/PageTitle'; import EditContent from '@community-tab/components/edit-content/EditContent'; import EditTitle from '@community-tab/components/edit-title/EditTitle'; -const Edit = () => { +export type EditProps = { + studyId: StudyId; + articleId: ArticleId; +}; + +const Edit: React.FC = ({ studyId, articleId }) => { const formMethods = useForm(); const navigate = useNavigate(); - const { studyId, articleId } = useParams<{ studyId: string; articleId: string }>(); - const numStudyId = Number(studyId); - const numArticleId = Number(articleId); - const getCommunityArticleQueryResult = useGetCommunityArticle(numStudyId, numArticleId); + const getCommunityArticleQueryResult = useGetCommunityArticle({ studyId, articleId }); const { mutateAsync } = usePutCommunityArticle(); - const handleGoToArticlePageButtonClick = () => { - navigate(`${PATH.COMMUNITY_ARTICLE(studyId, articleId)}`); - }; - const onSubmit = async (_: React.FormEvent, submitResult: UseFormSubmitResult) => { const { values } = submitResult; if (!values) return; @@ -46,11 +46,11 @@ const Edit = () => { }, { onSuccess: () => { - alert('글 수정 완료!'); - navigate(PATH.COMMUNITY(numStudyId)); + alert('글을 수정했습니다 :D'); + navigate(`../${PATH.COMMUNITY}`); }, onError: () => { - alert('글 수정 실패!'); + alert('글을 수정하지 못했습니다. 다시 시도해주세요 :('); }, }, ); @@ -72,16 +72,11 @@ const Edit = () => { - - 돌아가기 - + + + 돌아가기 + + 수정하기 diff --git a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/publish/Publish.tsx b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/publish/Publish.tsx index a49ccbe98..a72927e66 100644 --- a/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/publish/Publish.tsx +++ b/frontend/src/pages/study-room-page/tabs/community-tab-panel/components/publish/Publish.tsx @@ -1,7 +1,9 @@ -import { useNavigate, useParams } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { PATH } from '@constants'; +import type { StudyId } from '@custom-types'; + import { usePostCommunityArticle } from '@api/community'; import { FormProvider, UseFormSubmitResult, useForm } from '@hooks/useForm'; @@ -15,36 +17,34 @@ import PageTitle from '@components/page-title/PageTitle'; import PublishContent from '@community-tab/components/publish-content/PublishContent'; import PublishTitle from '@community-tab/components/publish-title/PublishTitle'; -const Publish = () => { +export type PublishProps = { + studyId: StudyId; +}; + +const Publish: React.FC = ({ studyId }) => { const formMethods = useForm(); const navigate = useNavigate(); - const { studyId } = useParams<{ studyId: string }>(); const { mutateAsync } = usePostCommunityArticle(); - const handleGoToArticleListPageButtonClick = () => { - navigate(`${PATH.COMMUNITY()}`); - }; - const onSubmit = async (_: React.FormEvent, submitResult: UseFormSubmitResult) => { const { values } = submitResult; if (!values) return; const { title, content } = values; - const numStudyId = Number(studyId); mutateAsync( { - studyId: numStudyId, + studyId, title, content, }, { onSuccess: () => { - alert('글 작성 완료!'); - navigate(PATH.COMMUNITY(numStudyId)); + alert('글을 작성했습니다. :D'); + navigate(`../${PATH.COMMUNITY}`); // TODO: 생성한 게시글 상세 페이지로 이동 }, onError: () => { - alert('글 작성 실패!'); + alert('글을 작성하지 못했습니다. 다시 시도해주세요. :('); }, }, ); @@ -58,16 +58,11 @@ const Publish = () => { - - 돌아가기 - + + + 돌아가기 + + 등록하기 diff --git a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel.stories.tsx b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel.stories.tsx index 90f163228..d4c7212e6 100644 --- a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel.stories.tsx +++ b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel.stories.tsx @@ -1,6 +1,6 @@ import type { Story } from '@storybook/react'; -import LinkRoomTabPanel from '@study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel'; +import LinkRoomTabPanel from '@link-tab/LinkRoomTabPanel'; export default { title: 'Pages/StudyRoomPage/LinkRoomTabPanel', diff --git a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel.tsx b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel.tsx index a2b724912..4112177ca 100644 --- a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel.tsx +++ b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel.tsx @@ -7,10 +7,10 @@ import InfiniteScroll from '@components/infinite-scroll/InfiniteScroll'; import ModalPortal from '@components/modal/Modal'; import Wrapper from '@components/wrapper/Wrapper'; -import * as S from '@study-room-page/tabs/link-room-tab-panel/LinkRoomTabPanel.style'; -import LinkForm from '@study-room-page/tabs/link-room-tab-panel/components/link-form/LinkForm'; -import LinkItem from '@study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem'; -import { useLinkRoomTabPanel } from '@study-room-page/tabs/link-room-tab-panel/hooks/useLinkRoomTabPanel'; +import * as S from '@link-tab/LinkRoomTabPanel.style'; +import LinkForm from '@link-tab/components/link-form/LinkForm'; +import LinkItem from '@link-tab/components/link-item/LinkItem'; +import { useLinkRoomTabPanel } from '@link-tab/hooks/useLinkRoomTabPanel'; const LinkRoomTabPanel: React.FC = () => { const { @@ -25,7 +25,7 @@ const LinkRoomTabPanel: React.FC = () => { } = useLinkRoomTabPanel(); const renderLinkList = () => { - const { data, isError, isSuccess, fetchNextPage } = infiniteLinksQueryResult; + const { data, isError, isSuccess, isFetching, fetchNextPage } = infiniteLinksQueryResult; if (isError || !isSuccess) { return
    에러가 발생했습니다
    ; } @@ -37,7 +37,7 @@ const LinkRoomTabPanel: React.FC = () => { } return ( - + {links.map(link => (
  • diff --git a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-edit-form/LinkEditForm.stories.tsx b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-edit-form/LinkEditForm.stories.tsx index d0d4eb3ee..c1bae729f 100644 --- a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-edit-form/LinkEditForm.stories.tsx +++ b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-edit-form/LinkEditForm.stories.tsx @@ -2,9 +2,7 @@ import type { Story } from '@storybook/react'; import { noop } from '@utils'; -import LinkEditForm, { - type LinkEditFormProps, -} from '@study-room-page/tabs/link-room-tab-panel/components/link-edit-form/LinkEditForm'; +import LinkEditForm, { type LinkEditFormProps } from '@link-tab/components/link-edit-form/LinkEditForm'; export default { title: 'Pages/StudyRoomPage/LinkEditForm', diff --git a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-form/LinkForm.stories.tsx b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-form/LinkForm.stories.tsx index 5d51b58b5..ac7f28956 100644 --- a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-form/LinkForm.stories.tsx +++ b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-form/LinkForm.stories.tsx @@ -2,7 +2,7 @@ import type { Story } from '@storybook/react'; import { noop } from '@utils'; -import LinkForm, { type LinkFormProps } from '@study-room-page/tabs/link-room-tab-panel/components/link-form/LinkForm'; +import LinkForm, { type LinkFormProps } from '@link-tab/components/link-form/LinkForm'; export default { title: 'Pages/StudyRoomPage/LinkForm', diff --git a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem.stories.tsx b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem.stories.tsx index d5064df3c..f0741ba4c 100644 --- a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem.stories.tsx +++ b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem.stories.tsx @@ -1,7 +1,7 @@ import type { Story } from '@storybook/react'; -import LinkItem from '@study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem'; -import type { LinkItemProps } from '@study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem'; +import LinkItem from '@link-tab/components/link-item/LinkItem'; +import type { LinkItemProps } from '@link-tab/components/link-item/LinkItem'; export default { title: 'Pages/StudyRoomPage/LinkItem', diff --git a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem.tsx b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem.tsx index 634b280ba..0a45ca97b 100644 --- a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem.tsx +++ b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-item/LinkItem.tsx @@ -11,10 +11,10 @@ import DropDownBox from '@components/drop-down-box/DropDownBox'; import { MeatballMenuIcon } from '@components/icons'; import ModalPortal from '@components/modal/Modal'; -import LinkEditForm from '@study-room-page/tabs/link-room-tab-panel/components/link-edit-form/LinkEditForm'; -import { useLinkItem } from '@study-room-page/tabs/link-room-tab-panel/components/link-item/hooks/useLinkItem'; -import LinkPreview from '@study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview'; -import UserDescription from '@study-room-page/tabs/link-room-tab-panel/components/user-description/UserDescription'; +import LinkEditForm from '@link-tab/components/link-edit-form/LinkEditForm'; +import { useLinkItem } from '@link-tab/components/link-item/hooks/useLinkItem'; +import LinkPreview from '@link-tab/components/link-preview/LinkPreview'; +import UserDescription from '@link-tab/components/user-description/UserDescription'; export type LinkItemProps = Pick & { studyId: StudyId; diff --git a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview.stories.tsx b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview.stories.tsx index 189fce1d4..6d446540c 100644 --- a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview.stories.tsx +++ b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview.stories.tsx @@ -1,7 +1,7 @@ import type { Story } from '@storybook/react'; -import LinkPreview from '@study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview'; -import type { LinkPreviewProps } from '@study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview'; +import LinkPreview from '@link-tab/components/link-preview/LinkPreview'; +import type { LinkPreviewProps } from '@link-tab/components/link-preview/LinkPreview'; export default { title: 'Pages/StudyRoomPage/LinkPreview', diff --git a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview.tsx b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview.tsx index 2305430c2..481dcb668 100644 --- a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview.tsx +++ b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview.tsx @@ -6,7 +6,7 @@ import Card from '@components/card/Card'; import { RightUpArrowIcon } from '@components/icons'; import Image from '@components/image/Image'; -import * as S from '@study-room-page/tabs/link-room-tab-panel/components/link-preview/LinkPreview.style'; +import * as S from '@link-tab/components/link-preview/LinkPreview.style'; export type LinkPreviewProps = { previewResult: ApiLinkPreview['get']['responseData']; diff --git a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/user-description/UserDescription.stories.tsx b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/user-description/UserDescription.stories.tsx index 521e2e630..5ae293018 100644 --- a/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/user-description/UserDescription.stories.tsx +++ b/frontend/src/pages/study-room-page/tabs/link-room-tab-panel/components/user-description/UserDescription.stories.tsx @@ -1,7 +1,7 @@ import type { Story } from '@storybook/react'; -import UserDescription from '@study-room-page/tabs/link-room-tab-panel/components/user-description/UserDescription'; -import type { UserDescriptionProps } from '@study-room-page/tabs/link-room-tab-panel/components/user-description/UserDescription'; +import UserDescription from '@link-tab/components/user-description/UserDescription'; +import type { UserDescriptionProps } from '@link-tab/components/user-description/UserDescription'; export default { title: 'Pages/StudyRoomPage/UserDescription', diff --git a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/NoticeTabPanel.tsx b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/NoticeTabPanel.tsx index 5a868ba81..96e4f7913 100644 --- a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/NoticeTabPanel.tsx +++ b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/NoticeTabPanel.tsx @@ -1,9 +1,4 @@ -import ArticleList from '@notice-tab/components/article-list/ArticleList'; -import Article from '@notice-tab/components/article/Article'; -import Edit from '@notice-tab/components/edit/Edit'; -import Publish from '@notice-tab/components/publish/Publish'; -import usePermission from '@notice-tab/hooks/usePermission'; -import { useNavigate, useParams } from 'react-router-dom'; +import { Link, useLocation, useParams } from 'react-router-dom'; import { PATH } from '@constants'; @@ -14,36 +9,38 @@ import Divider from '@components/divider/Divider'; import Flex from '@components/flex/Flex'; import Wrapper from '@components/wrapper/Wrapper'; -type NoticeTabPanelProps = { - studyId: number; -}; +import ArticleList from '@notice-tab/components/article-list/ArticleList'; +import Article from '@notice-tab/components/article/Article'; +import Edit from '@notice-tab/components/edit/Edit'; +import Publish from '@notice-tab/components/publish/Publish'; +import usePermission from '@notice-tab/hooks/usePermission'; + +const NoticeTabPanel: React.FC = () => { + const location = useLocation(); + const { studyId: _studyId, articleId: _articleId } = useParams<{ studyId: string; articleId: string }>(); + const [studyId, articleId] = [Number(_studyId), Number(_articleId)]; -const NoticeTabPanel: React.FC = ({ studyId }) => { - const { articleId } = useParams<{ articleId: string }>(); - const navigate = useNavigate(); const { hasPermission: isOwner } = usePermission(studyId, 'OWNER'); - const lastPath = window.location.pathname.split('/').at(-1); + const lastPath = location.pathname.split('/').at(-1); const isPublishPage = lastPath === 'publish'; const isEditPage = lastPath === 'edit'; const isArticleDetailPage = !!(articleId && !isPublishPage && !isEditPage); const isListPage = !!(!articleId && !isPublishPage && !isEditPage && !isArticleDetailPage); - const handleGoToPublishPageButtonClick = () => { - navigate(`${PATH.NOTICE_PUBLISH(studyId)}`); - }; - const renderArticleListPage = () => { return ( <> {isOwner && ( - - 글쓰기 - + + + 글쓰기 + + )} - + ); }; @@ -57,10 +54,10 @@ const NoticeTabPanel: React.FC = ({ studyId }) => { return
    ; } if (isPublishPage) { - return ; + return ; } if (isEditPage) { - return ; + return ; } }; diff --git a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/article-list-item/ArticleListItem.tsx b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/article-list-item/ArticleListItem.tsx index 3db088467..63b13e7a6 100644 --- a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/article-list-item/ArticleListItem.tsx +++ b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/article-list-item/ArticleListItem.tsx @@ -8,7 +8,7 @@ import { theme } from '@styles/theme'; import Flex from '@components/flex/Flex'; import UserInfoItem from '@components/user-info-item/UserInfoItem'; -import * as S from '@study-room-page/tabs/notice-tab-panel/components/article-list-item/ArticleListItem.style'; +import * as S from '@notice-tab/components/article-list-item/ArticleListItem.style'; export type ArticleListItemProps = Pick; diff --git a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/article-list/ArticleList.tsx b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/article-list/ArticleList.tsx index bbd765811..82c6b18a3 100644 --- a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/article-list/ArticleList.tsx +++ b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/article-list/ArticleList.tsx @@ -1,20 +1,25 @@ -import ArticleListItem from '@notice-tab/components/article-list-item/ArticleListItem'; -import Pagination from '@notice-tab/components/pagination/Pagination'; import { useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { PATH } from '@constants'; +import type { StudyId } from '@custom-types'; + import { useGetNoticeArticles } from '@api/notice'; import Divider from '@components/divider/Divider'; import Flex from '@components/flex/Flex'; -const ArticleList: React.FC = () => { - const { studyId } = useParams<{ studyId: string }>(); - const numStudyId = Number(studyId); +import ArticleListItem from '@notice-tab/components/article-list-item/ArticleListItem'; +import Pagination from '@notice-tab/components/pagination/Pagination'; + +export type ArticleListProps = { + studyId: StudyId; +}; + +const ArticleList: React.FC = ({ studyId }) => { const [page, setPage] = useState(1); - const { isFetching, isSuccess, isError, data } = useGetNoticeArticles(numStudyId, page); + const { isFetching, isSuccess, isError, data } = useGetNoticeArticles({ studyId, page }); if (isFetching) { return
    Loading...
    ; @@ -35,7 +40,7 @@ const ArticleList: React.FC = () => {
      {articles.map(article => (
    • - + = ({ studyId, articleId }) => { - const { isFetching, isSuccess, isError, data } = useGetNoticeArticle(studyId, articleId); +const Article: React.FC = ({ studyId, articleId }) => { + const { isFetching, isSuccess, isError, data } = useGetNoticeArticle({ studyId, articleId }); const getUserInformationQueryResult = useGetUserInformation(); const { mutateAsync } = useDeleteNoticeArticle(); const navigate = useNavigate(); - const {} = useAuth(); - const handleBackToArticleListButtonClick = () => { - navigate(`${PATH.COMMUNITY(studyId)}`); - }; const handleDeleteArticleButtonClick = () => { mutateAsync( @@ -41,19 +34,15 @@ const Article: FC = ({ studyId, articleId }) => { { onSuccess: () => { alert('성공적으로 삭제했습니다'); - navigate(`${PATH.COMMUNITY(studyId)}`); + navigate(`../${PATH.NOTICE}`); }, onError: () => { - alert('알수 없는 에러가 발생했습니다'); + alert('알 수 없는 에러가 발생했습니다'); }, }, ); }; - const handleEditArticleButtonClick = () => { - navigate(`${PATH.COMMUNITY_EDIT(studyId, articleId)}`); - }; - const renderModifierButtons = () => { if (!getUserInformationQueryResult.isSuccess || getUserInformationQueryResult.isError) return; if (!data?.author.username) return; @@ -61,9 +50,11 @@ const Article: FC = ({ studyId, articleId }) => { return ( - - 글수정 - + + + 글수정 + + = ({ studyId, articleId }) => {
  • - - 목록보기 - + + + 목록보기 + + ); } diff --git a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/edit/Edit.tsx b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/edit/Edit.tsx index dbd462776..a0c737e35 100644 --- a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/edit/Edit.tsx +++ b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/edit/Edit.tsx @@ -1,11 +1,10 @@ -import EditContent from '@notice-tab/components/edit-content/EditContent'; -import EditTitle from '@notice-tab/components/edit-title/EditTitle'; -import usePermission from '@notice-tab/hooks/usePermission'; import { useEffect } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { PATH } from '@constants'; +import type { ArticleId, StudyId } from '@custom-types'; + import { useGetNoticeArticle, usePutNoticeArticle } from '@api/notice'; import { FormProvider, UseFormSubmitResult, useForm } from '@hooks/useForm'; @@ -16,14 +15,20 @@ import Divider from '@components/divider/Divider'; import Form from '@components/form/Form'; import PageTitle from '@components/page-title/PageTitle'; -const Edit = () => { +import EditContent from '@notice-tab/components/edit-content/EditContent'; +import EditTitle from '@notice-tab/components/edit-title/EditTitle'; +import usePermission from '@notice-tab/hooks/usePermission'; + +export type EditProps = { + studyId: StudyId; + articleId: ArticleId; +}; + +const Edit: React.FC = ({ studyId, articleId }) => { const formMethods = useForm(); const navigate = useNavigate(); - const { studyId, articleId } = useParams() as { studyId: string; articleId: string }; - const numStudyId = Number(studyId); - const numArticleId = Number(articleId); - const getNoticeArticleQueryResult = useGetNoticeArticle(numStudyId, numArticleId); + const getNoticeArticleQueryResult = useGetNoticeArticle({ studyId, articleId }); const { mutateAsync } = usePutNoticeArticle(); const { isFetching, hasPermission } = usePermission(studyId, 'OWNER'); @@ -33,13 +38,9 @@ const Edit = () => { if (hasPermission) return; alert('접근할 수 없습니다!'); - navigate(PATH.NOTICE(studyId)); + navigate(`../${PATH.NOTICE}`); }, [studyId, navigate, isFetching, hasPermission]); - const handleGoToArticlePageButtonClick = () => { - navigate(`${PATH.NOTICE_ARTICLE(studyId, articleId)}`); - }; - const onSubmit = async (_: React.FormEvent, submitResult: UseFormSubmitResult) => { const { values } = submitResult; if (!values) return; @@ -57,8 +58,8 @@ const Edit = () => { }, { onSuccess: () => { - alert('글 수정 완료!'); - navigate(PATH.NOTICE(numStudyId)); + alert('글을 수정했습니다 :D'); + navigate(`../${PATH.NOTICE}`); }, onError: () => { alert('글 수정 실패!'); @@ -83,16 +84,11 @@ const Edit = () => { - - 돌아가기 - + + + 돌아가기 + + 수정하기 diff --git a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/publish/Publish.tsx b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/publish/Publish.tsx index 2edc2ba16..63decf6af 100644 --- a/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/publish/Publish.tsx +++ b/frontend/src/pages/study-room-page/tabs/notice-tab-panel/components/publish/Publish.tsx @@ -1,11 +1,10 @@ -import PublishContent from '@notice-tab/components/publish-content/PublishContent'; -import PublishTitle from '@notice-tab/components/publish-title/PublishTitle'; -import usePermission from '@notice-tab/hooks/usePermission'; import { useEffect } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { PATH } from '@constants'; +import type { StudyId } from '@custom-types'; + import { usePostNoticeArticle } from '@api/notice'; import { FormProvider, UseFormSubmitResult, useForm } from '@hooks/useForm'; @@ -16,10 +15,17 @@ import Divider from '@components/divider/Divider'; import Form from '@components/form/Form'; import PageTitle from '@components/page-title/PageTitle'; -const Publish = () => { +import PublishContent from '@notice-tab/components/publish-content/PublishContent'; +import PublishTitle from '@notice-tab/components/publish-title/PublishTitle'; +import usePermission from '@notice-tab/hooks/usePermission'; + +export type PublishProps = { + studyId: StudyId; +}; + +const Publish: React.FC = ({ studyId }) => { const formMethods = useForm(); const navigate = useNavigate(); - const { studyId } = useParams() as { studyId: string }; const { mutateAsync } = usePostNoticeArticle(); const { isFetching, isError, hasPermission } = usePermission(studyId, 'OWNER'); @@ -28,13 +34,9 @@ const Publish = () => { if (hasPermission) return; alert('접근할 수 없습니다!'); - navigate(PATH.NOTICE(studyId)); + navigate(`../${PATH.NOTICE}`); }, [studyId, navigate, isFetching, hasPermission]); - const handleGoToArticleListPageButtonClick = () => { - navigate(`${PATH.NOTICE()}`); - }; - const onSubmit = async (_: React.FormEvent, submitResult: UseFormSubmitResult) => { const { values } = submitResult; if (!values) return; @@ -50,22 +52,22 @@ const Publish = () => { }, { onSuccess: () => { - alert('글 작성 완료!'); - navigate(PATH.NOTICE(numStudyId)); + alert('글을 작성했습니다. :D'); + navigate(`../${PATH.NOTICE}`); // TODO: 생성한 게시글 상세 페이지로 이동 }, onError: () => { - alert('글 작성 실패!'); + alert('글을 작성하지 못했습니다. 다시 시도해주세요. :('); }, }, ); }; if (isFetching) { - return
    유저 정보 가져오는중...
    ; + return
    유저 정보 가져오는 중...
    ; } if (isError) { - return
    유저 정보를 가져오는도중 에러를 만났습니다
    ; + return
    유저 정보를 가져오는 도중 에러가 발생했습니다.
    ; } return ( @@ -76,16 +78,11 @@ const Publish = () => { - - 돌아가기 - + + + 돌아가기 + + 등록하기 diff --git a/frontend/src/pages/study-room-page/tabs/review-tab-panel/ReviewTabPanel.tsx b/frontend/src/pages/study-room-page/tabs/review-tab-panel/ReviewTabPanel.tsx index e0973261d..3cfe38e49 100644 --- a/frontend/src/pages/study-room-page/tabs/review-tab-panel/ReviewTabPanel.tsx +++ b/frontend/src/pages/study-room-page/tabs/review-tab-panel/ReviewTabPanel.tsx @@ -1,4 +1,5 @@ import { useEffect } from 'react'; +import { useParams } from 'react-router-dom'; import { useGetStudyReviews } from '@api/reviews'; @@ -7,14 +8,13 @@ import { useUserInfo } from '@hooks/useUserInfo'; import Divider from '@components/divider/Divider'; import Wrapper from '@components/wrapper/Wrapper'; -import ReviewForm from '@study-room-page/tabs/review-tab-panel/components/reivew-form/ReviewForm'; -import ReviewComment from '@study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment'; +import ReviewForm from '@review-tab/components/reivew-form/ReviewForm'; +import ReviewComment from '@review-tab/components/review-comment/ReviewComment'; -export type ReviewTabPanelProps = { - studyId: number; -}; +const ReviewTabPanel: React.FC = () => { + const { studyId: _studyId } = useParams<{ studyId: string }>(); + const studyId = Number(_studyId); -const ReviewTabPanel: React.FC = ({ studyId }) => { const { data, isFetching, refetch, isError, isSuccess } = useGetStudyReviews({ studyId }); const { userInfo, fetchUserInfo } = useUserInfo(); diff --git a/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/reivew-form/ReviewForm.stories.tsx b/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/reivew-form/ReviewForm.stories.tsx index 68023c1b0..52d8ff3d1 100644 --- a/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/reivew-form/ReviewForm.stories.tsx +++ b/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/reivew-form/ReviewForm.stories.tsx @@ -1,7 +1,7 @@ import type { Story } from '@storybook/react'; -import type { ReviewFormProps } from '@study-room-page/tabs/review-tab-panel/components/reivew-form/ReviewForm'; -import ReviewForm from '@study-room-page/tabs/review-tab-panel/components/reivew-form/ReviewForm'; +import type { ReviewFormProps } from '@review-tab/components/reivew-form/ReviewForm'; +import ReviewForm from '@review-tab/components/reivew-form/ReviewForm'; export default { title: 'Pages/StudyRoomPage/ReviewForm', diff --git a/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment.stories.tsx b/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment.stories.tsx index 2e98db98b..d9305e593 100644 --- a/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment.stories.tsx +++ b/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment.stories.tsx @@ -1,7 +1,7 @@ import type { Story } from '@storybook/react'; -import type { ReviewCommentProps } from '@study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment'; -import ReviewComment from '@study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment'; +import ReviewComment from '@review-tab/components/review-comment/ReviewComment'; +import type { ReviewCommentProps } from '@review-tab/components/review-comment/ReviewComment'; export default { title: 'Pages/StudyRoomPage/ReviewComment', diff --git a/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment.tsx b/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment.tsx index 9898ce010..215277c4a 100644 --- a/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment.tsx +++ b/frontend/src/pages/study-room-page/tabs/review-tab-panel/components/review-comment/ReviewComment.tsx @@ -13,8 +13,8 @@ import Flex from '@components/flex/Flex'; import { KebabMenuIcon } from '@components/icons'; import UserInfoItem from '@components/user-info-item/UserInfoItem'; -import useReviewComment from '@study-room-page/tabs/review-tab-panel/components/review-comment/useReviewComment'; -import ReviewEditForm from '@study-room-page/tabs/review-tab-panel/components/review-edit-form/ReviewEditForm'; +import useReviewComment from '@review-tab/components/review-comment/useReviewComment'; +import ReviewEditForm from '@review-tab/components/review-edit-form/ReviewEditForm'; export type ReviewCommentProps = { id: ReviewId; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 561b93c01..b531db7ae 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -33,8 +33,10 @@ "@create-study-page/*": ["pages/create-study-page/*"], "@edit-study-page/*": ["pages/edit-study-page/*"], "@my-study-page/*": ["pages/my-study-page/*"], - "@community-tab/*": ["pages/study-room-page/tabs/community-tab-panel/*"], "@notice-tab/*": ["pages/study-room-page/tabs/notice-tab-panel/*"], + "@community-tab/*": ["pages/study-room-page/tabs/community-tab-panel/*"], + "@link-tab/*": ["pages/study-room-page/tabs/link-room-tab-panel/*"], + "@review-tab/*": ["pages/study-room-page/tabs/review-tab-panel/*"], "@study-room-page/*": ["pages/study-room-page/*"], "@login-redirect-page/*": ["pages/login-redirect-page/*"], "@error-page/*": ["pages/error-page/*"], diff --git a/frontend/webpack/webpack.common.js b/frontend/webpack/webpack.common.js index 20e034f7f..0e62c0811 100644 --- a/frontend/webpack/webpack.common.js +++ b/frontend/webpack/webpack.common.js @@ -74,8 +74,10 @@ module.exports = { '@edit-study-page': resolve(__dirname, '../src/pages/edit-study-page'), '@my-study-page': resolve(__dirname, '../src/pages/my-study-page'), '@study-room-page': resolve(__dirname, '../src/pages/study-room-page'), - '@community-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/community-tab-panel'), '@notice-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/notice-tab-panel'), + '@community-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/community-tab-panel'), + '@link-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/link-room-tab-panel'), + '@review-tab': resolve(__dirname, '../src/pages/study-room-page/tabs/review-tab-panel'), '@error-page': resolve(__dirname, '../src/pages/error-page'), '@login-redirect-page': resolve(__dirname, '../src/pages/login-redirect-page'), '@layout': resolve(__dirname, '../src/layout'), diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 3d6b772d9..000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "2022-moamoa-2", - "lockfileVersion": 2, - "requires": true, - "packages": {} -}