diff --git a/frontend/src/apis/endpoints.ts b/frontend/src/apis/endpoints.ts index 5b251ae0e..47afe6dc6 100644 --- a/frontend/src/apis/endpoints.ts +++ b/frontend/src/apis/endpoints.ts @@ -55,6 +55,7 @@ export const REVIEW_GROUP_DATA_API_URL = `${serverUrl}/${VERSION2}/${REVIEW_GROU const endPoint = { postingReview: `${serverUrl}/${VERSION2}/reviews`, + gettingReviewInfoData: `${serverUrl}/${VERSION2}/reviews/summary`, gettingDetailedReview: (reviewId: number) => `${DETAILED_REVIEW_API_URL}/${reviewId}`, gettingDataToWriteReview: (reviewRequestCode: string) => `${REVIEW_WRITING_API_URL}/${REVIEW_WRITING_API_PARAMS.queryString.write}?${REVIEW_WRITING_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`, diff --git a/frontend/src/apis/review.ts b/frontend/src/apis/review.ts index 825f70283..df72b4709 100644 --- a/frontend/src/apis/review.ts +++ b/frontend/src/apis/review.ts @@ -1,4 +1,4 @@ -import { DetailReviewData, ReviewList, ReviewWritingFormResult, ReviewWritingFormData } from '@/types'; +import { DetailReviewData, ReviewList, ReviewWritingFormResult, ReviewWritingFormData, ReviewInfoData } from '@/types'; import createApiErrorMessage from './apiErrorMessageCreator'; import endPoint from './endpoints'; @@ -32,6 +32,24 @@ export const postReviewApi = async (formResult: ReviewWritingFormResult) => { return; }; +// 받은 리뷰들에 대한 정보(프로젝트 이름, 리뷰이, 받은 리뷰 개수) +export const getReviewInfoDataApi = async () => { + const response = await fetch(endPoint.gettingReviewInfoData, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }); + + if (!response.ok) { + throw new Error(createApiErrorMessage(response.status)); + } + + const data = await response.json(); + return data as ReviewInfoData; +}; + interface GetDetailedReviewApi { reviewId: number; } diff --git a/frontend/src/components/ReviewCard/index.tsx b/frontend/src/components/ReviewCard/index.tsx index f820a55a4..bcb71a803 100644 --- a/frontend/src/components/ReviewCard/index.tsx +++ b/frontend/src/components/ReviewCard/index.tsx @@ -3,7 +3,6 @@ import { Category } from '@/types'; import * as S from './styles'; interface ReviewCardProps { - projectName: string; createdAt: string; contentPreview: string; categories: Category[]; diff --git a/frontend/src/components/layouts/ReviewDisplayLayout/components/ReviewInfoSection/index.tsx b/frontend/src/components/layouts/ReviewDisplayLayout/components/ReviewInfoSection/index.tsx index d1bf463bf..1a8ab689a 100644 --- a/frontend/src/components/layouts/ReviewDisplayLayout/components/ReviewInfoSection/index.tsx +++ b/frontend/src/components/layouts/ReviewDisplayLayout/components/ReviewInfoSection/index.tsx @@ -6,10 +6,10 @@ export interface ReviewInfoSectionProps { revieweeName: string; isReviewList: boolean; projectName: string; - reviewCount?: number; + totalReviewCount?: number; } -const ReviewInfoSection = ({ projectName, revieweeName, reviewCount, isReviewList }: ReviewInfoSectionProps) => { +const ReviewInfoSection = ({ projectName, revieweeName, totalReviewCount, isReviewList }: ReviewInfoSectionProps) => { const revieweeNameWithParticle = calculateParticle({ target: revieweeName, particles: { withFinalConsonant: '이', withoutFinalConsonant: '가' }, @@ -17,7 +17,7 @@ const ReviewInfoSection = ({ projectName, revieweeName, reviewCount, isReviewLis const getReviewInfoMessage = () => { return isReviewList - ? `${revieweeNameWithParticle} 받은 ${reviewCount}개의 리뷰 목록이에요` + ? `${revieweeNameWithParticle} 받은 ${totalReviewCount}개의 리뷰 목록이에요` : `${revieweeNameWithParticle} 받은 리뷰를 질문별로 모아봤어요`; }; diff --git a/frontend/src/components/layouts/ReviewDisplayLayout/hooks/index.ts b/frontend/src/components/layouts/ReviewDisplayLayout/hooks/index.ts new file mode 100644 index 000000000..e5198fb52 --- /dev/null +++ b/frontend/src/components/layouts/ReviewDisplayLayout/hooks/index.ts @@ -0,0 +1,2 @@ +export { default as useReviewDisplayLayoutOptions } from './useReviewDisplayLayoutOptions/index'; +export { default as useReviewInfoData } from './useReviewInfoData/index'; diff --git a/frontend/src/components/layouts/ReviewDisplayLayout/hooks/useReviewDisplayLayoutOptions.ts b/frontend/src/components/layouts/ReviewDisplayLayout/hooks/useReviewDisplayLayoutOptions/index.ts similarity index 100% rename from frontend/src/components/layouts/ReviewDisplayLayout/hooks/useReviewDisplayLayoutOptions.ts rename to frontend/src/components/layouts/ReviewDisplayLayout/hooks/useReviewDisplayLayoutOptions/index.ts diff --git a/frontend/src/components/layouts/ReviewDisplayLayout/hooks/useReviewInfoData/index.tsx b/frontend/src/components/layouts/ReviewDisplayLayout/hooks/useReviewInfoData/index.tsx new file mode 100644 index 000000000..b48779a27 --- /dev/null +++ b/frontend/src/components/layouts/ReviewDisplayLayout/hooks/useReviewInfoData/index.tsx @@ -0,0 +1,21 @@ +import { useSuspenseQuery } from '@tanstack/react-query'; + +import { getReviewInfoDataApi } from '@/apis/review'; +import { REVIEW_QUERY_KEY } from '@/constants'; +import { ReviewInfoData } from '@/types'; + +const useReviewInfoData = () => { + const fetchReviewInfoData = async () => { + return await getReviewInfoDataApi(); + }; + + const { data } = useSuspenseQuery({ + queryKey: [REVIEW_QUERY_KEY.reviewInfoData], + queryFn: () => fetchReviewInfoData(), + staleTime: 60 * 60 * 1000, + }); + + return data; +}; + +export default useReviewInfoData; diff --git a/frontend/src/components/layouts/ReviewDisplayLayout/index.tsx b/frontend/src/components/layouts/ReviewDisplayLayout/index.tsx index 8c22cfbfc..73545864a 100644 --- a/frontend/src/components/layouts/ReviewDisplayLayout/index.tsx +++ b/frontend/src/components/layouts/ReviewDisplayLayout/index.tsx @@ -1,18 +1,17 @@ import { TopButton, OptionSwitch } from '@/components/common'; import { EssentialPropsWithChildren } from '@/types'; -import ReviewInfoSection, { ReviewInfoSectionProps } from './components/ReviewInfoSection'; -import useReviewDisplayLayoutOptions from './hooks/useReviewDisplayLayoutOptions'; +import ReviewInfoSection from './components/ReviewInfoSection'; +import { useReviewInfoData, useReviewDisplayLayoutOptions } from './hooks'; import * as S from './styles'; -const ReviewDisplayLayout = ({ - revieweeName, - projectName, - reviewCount, - isReviewList, - children, -}: EssentialPropsWithChildren) => { +interface ReviewDisplayLayoutProps { + isReviewList: boolean; +} + +const ReviewDisplayLayout = ({ isReviewList, children }: EssentialPropsWithChildren) => { const reviewDisplayLayoutOptions = useReviewDisplayLayoutOptions(); + const { revieweeName, projectName, totalReviewCount } = useReviewInfoData(); return ( @@ -20,7 +19,7 @@ const ReviewDisplayLayout = ({ diff --git a/frontend/src/constants/queryKey.ts b/frontend/src/constants/queryKey.ts index e89c803dc..f4f89e6fb 100644 --- a/frontend/src/constants/queryKey.ts +++ b/frontend/src/constants/queryKey.ts @@ -1,9 +1,9 @@ -// TODO: 내용이 배열이 아니므로 단수형으로 수정하기 export const REVIEW_QUERY_KEY = { detailedReview: 'detailedReview', reviews: 'reviews', writingReviewInfo: 'writingReviewInfo', postReview: 'postReview', + reviewInfoData: 'reviewInfoData', }; export const GROUP_QUERY_KEY = { diff --git a/frontend/src/mocks/handlers/review.ts b/frontend/src/mocks/handlers/review.ts index 2eb0e3002..43b6bcb2a 100644 --- a/frontend/src/mocks/handlers/review.ts +++ b/frontend/src/mocks/handlers/review.ts @@ -15,6 +15,7 @@ import { REVIEW_QUESTION_DATA, REVIEW_LIST, MOCK_AUTH_TOKEN_NAME, + MOCK_REVIEW_INFO_DATA, } from '../mockData'; export const PAGE = { @@ -22,6 +23,15 @@ export const PAGE = { firstPageStartIndex: 0, }; +const getReviewInfoData = () => + http.get(endPoint.gettingReviewInfoData, async ({ cookies }) => { + if (!cookies[MOCK_AUTH_TOKEN_NAME]) { + return HttpResponse.json({ error: '인증 관련 쿠키 없음' }, { status: 401 }); + } + + return HttpResponse.json(MOCK_REVIEW_INFO_DATA); + }); + const getDetailedReview = () => http.get(new RegExp(`^${DETAILED_REVIEW_API_URL}/\\d+$`), async ({ request, cookies }) => { // authToken 쿠키 확인 @@ -43,17 +53,21 @@ const getDetailedReview = () => }); const getDataToWriteReview = () => - http.get(new RegExp(`^${REVIEW_WRITING_API_URL}`), async ({ request }) => { - //요청 url에서 reviewId, memberId 추출 - const url = new URL(request.url); - const urlRequestCode = url.searchParams.get(REVIEW_WRITING_API_PARAMS.queryString.reviewRequestCode); - - if (REVIEW_REQUEST_CODE === urlRequestCode) { - return HttpResponse.json(REVIEW_QUESTION_DATA); - } - return HttpResponse.json({ error: '잘못된 리뷰 작성 데이터 요청' }, { status: 404 }); - }); - + http.get( + new RegExp(`^${REVIEW_WRITING_API_URL}/${REVIEW_WRITING_API_PARAMS.queryString.write}`), + async ({ request }) => { + //요청 url에서 reviewId, memberId 추출 + const url = new URL(request.url); + const urlRequestCode = url.searchParams.get(REVIEW_WRITING_API_PARAMS.queryString.reviewRequestCode); + + if (REVIEW_REQUEST_CODE === urlRequestCode) { + return HttpResponse.json(REVIEW_QUESTION_DATA); + } + return HttpResponse.json({ error: '잘못된 리뷰 작성 데이터 요청' }, { status: 404 }); + }, + ); + +// TODO: 추후 getReviewList API에서 리뷰 정보(이름, 개수...)를 내려주지 않는 경우 핸들러도 수정 필요 const getReviewList = (lastReviewId: number | null, size: number) => { return http.get(endPoint.gettingReviewList(lastReviewId, size), async ({ request, cookies }) => { // authToken 쿠키 확인 @@ -90,6 +104,12 @@ const postReview = () => return HttpResponse.json({ message: 'post 성공' }, { status: 201 }); }); -const reviewHandler = [getDetailedReview(), getReviewList(null, 10), getDataToWriteReview(), postReview()]; +const reviewHandler = [ + getDetailedReview(), + getReviewList(null, 10), + getDataToWriteReview(), + postReview(), + getReviewInfoData(), +]; export default reviewHandler; diff --git a/frontend/src/mocks/mockData/index.ts b/frontend/src/mocks/mockData/index.ts index 930b9a081..1eb86e462 100644 --- a/frontend/src/mocks/mockData/index.ts +++ b/frontend/src/mocks/mockData/index.ts @@ -3,3 +3,4 @@ export * from './group'; export * from './reviewListMockData'; export * from './reviewWriting/reviewFormResultData'; export * from './reviewWriting/reviewQuestionData'; +export * from './reviewInfoData'; diff --git a/frontend/src/mocks/mockData/reviewInfoData.ts b/frontend/src/mocks/mockData/reviewInfoData.ts new file mode 100644 index 000000000..4078b9156 --- /dev/null +++ b/frontend/src/mocks/mockData/reviewInfoData.ts @@ -0,0 +1,5 @@ +export const MOCK_REVIEW_INFO_DATA = { + projectName: '리뷰미', + revieweeName: '산초', + totalReviewCount: 500, +}; diff --git a/frontend/src/pages/ReviewCollectionPage/index.tsx b/frontend/src/pages/ReviewCollectionPage/index.tsx index 3bd098ace..a4fc17672 100644 --- a/frontend/src/pages/ReviewCollectionPage/index.tsx +++ b/frontend/src/pages/ReviewCollectionPage/index.tsx @@ -21,7 +21,7 @@ const ReviewCollectionPage = () => { return ( - + { navigate(`/${ROUTE.detailedReview}/${reviewRequestCode}/${id}`); }; - const { projectName, revieweeName } = data.pages[0]; const isLastPage = data.pages[data.pages.length - 1].isLastPage; const reviews = data.pages.flatMap((page) => page.reviews); @@ -36,7 +35,7 @@ const PageContents = () => { return ( isSuccess && ( - + {reviews.length === 0 ? ( ) : ( @@ -46,7 +45,6 @@ const PageContents = () => { return (