diff --git a/frontend/src/apis/apis.ts b/frontend/src/apis/apis.ts index c01986801..2251a57a2 100644 --- a/frontend/src/apis/apis.ts +++ b/frontend/src/apis/apis.ts @@ -4,6 +4,7 @@ import { GetDetailedRunnerPostResponse, GetRunnerPostResponse, ReviewStatus, + getRunnerPostRequestParams, } from '@/types/runnerPost'; import { GetSearchTagResponse } from '@/types/tags'; import { @@ -12,13 +13,14 @@ import { GetSupporterProfileResponse, PatchRunnerProfileRequest, PatchSupporterProfileRequest, + getSupporterPostRequestParams, } from '@/types/profile'; -import { GetMyPagePostResponse } from '@/types/myPage'; +import { GetMyPagePostResponse, getMyPostRequestParams } from '@/types/myPage'; import { PostFeedbackRequest } from '@/types/feedback'; import { GetSupporterCandidateResponse } from '@/types/supporterCandidate'; import { GetNotificationResponse } from '@/types/notification'; -export const getRunnerPost = (limit: number, reviewStatus?: ReviewStatus, cursor?: number, tagName?: string) => { +export const getRunnerPost = ({ limit, reviewStatus, cursor, tagName }: getRunnerPostRequestParams) => { const params = new URLSearchParams({ limit: limit.toString(), ...(cursor && { cursor: cursor.toString() }), @@ -29,26 +31,37 @@ export const getRunnerPost = (limit: number, reviewStatus?: ReviewStatus, cursor return request.get(`/posts/runner?${params.toString()}`, false); }; -export const getMyRunnerPost = (size: number, page: number, reviewStatus?: ReviewStatus) => { +export const getMyRunnerPost = ({ limit, cursor, reviewStatus }: getMyPostRequestParams) => { const params = new URLSearchParams({ - size: size.toString(), - ...(page && { page: page.toString() }), + limit: limit.toString(), + ...(cursor && { cursor: cursor.toString() }), ...(reviewStatus && { reviewStatus }), }); return request.get(`/posts/runner/me/runner?${params.toString()}`, true); }; -export const getMySupporterPost = (size: number, page: number, reviewStatus?: ReviewStatus) => { +export const getMySupporterPost = ({ limit, cursor, reviewStatus }: getMyPostRequestParams) => { const params = new URLSearchParams({ - size: size.toString(), - ...(page && { page: page.toString() }), + limit: limit.toString(), + ...(cursor && { cursor: cursor.toString() }), ...(reviewStatus && { reviewStatus }), }); return request.get(`/posts/runner/me/supporter?${params.toString()}`, true); }; +export const getOtherSupporterPost = ({ limit, cursor, supporterId }: getSupporterPostRequestParams) => { + const params = new URLSearchParams({ + limit: limit.toString(), + supporterId: supporterId.toString(), + reviewStatus: 'DONE', + ...(cursor && { cursor: cursor.toString() }), + }); + + return request.get(`/posts/runner/search?${params.toString()}`, false); +}; + export const getSearchTag = (keyword: string) => { return request.get(`/tags/search?tagName=${keyword}`, false); }; @@ -85,15 +98,6 @@ export const getOtherSupporterProfile = (userId: number) => { return request.get(`/profile/supporter/${userId}`, false); }; -export const getOtherSupporterPost = (supporterId: number) => { - const params = new URLSearchParams([ - ['supporterId', supporterId.toString()], - ['reviewStatus', 'DONE'], - ]); - - return request.get(`/posts/runner/search?${params.toString()}`, false); -}; - export const postRunnerPostCreation = (formData: CreateRunnerPostRequest) => { const body = JSON.stringify(formData); return request.post(`/posts/runner`, body); @@ -147,4 +151,4 @@ export const deleteRunnerPost = (runnerPostId: number) => { export const deleteNotification = (notificationsId: number) => { return request.delete(`/notifications/${notificationsId}`); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/RunnerPost/RunnerPostFilter/RunnerPostFilter.tsx b/frontend/src/components/RunnerPost/RunnerPostFilter/RunnerPostFilter.tsx index 1115f260b..ee2832d39 100644 --- a/frontend/src/components/RunnerPost/RunnerPostFilter/RunnerPostFilter.tsx +++ b/frontend/src/components/RunnerPost/RunnerPostFilter/RunnerPostFilter.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { css, keyframes, styled } from 'styled-components'; -import { REVIEW_STATUS_LABEL_TEXT } from '@/constants'; +import { REVIEW_STATUS_FILTER_TEXT, REVIEW_STATUS_LABEL_TEXT } from '@/constants'; interface Props { reviewStatus: string; @@ -11,7 +11,7 @@ const RunnerPostFilter = ({ reviewStatus, handleClickRadioButton }: Props) => { return ( - {Object.entries(REVIEW_STATUS_LABEL_TEXT).map(([value, text]) => ( + {Object.entries(REVIEW_STATUS_FILTER_TEXT).map(([value, text]) => ( >; + reviewStatus: ReviewStatus | null; + setReviewStatus: React.Dispatch>; setEnteredTag: React.Dispatch>; } @@ -39,11 +39,11 @@ const RunnerPostSearchBox = ({ reviewStatus, setReviewStatus, setEnteredTag }: P }; const handleClickRadioButton = (e: React.ChangeEvent) => { - const clickedStatus = e.target.value as ReviewStatus; + const clickedStatus = e.target.value as ReviewStatusFilter; if (clickedStatus === reviewStatus) return; - setReviewStatus(clickedStatus); + clickedStatus === 'ALL' ? setReviewStatus(null) : setReviewStatus(clickedStatus); }; const handleInputFocus = () => { @@ -118,7 +118,7 @@ const RunnerPostSearchBox = ({ reviewStatus, setReviewStatus, setEnteredTag }: P return ( - + = { + ALL: '전체', + ...REVIEW_STATUS_LABEL_TEXT, +}; + export const REVIEW_STATUS: ReviewStatus[] = Object.keys(REVIEW_STATUS_LABEL_TEXT) as ReviewStatus[]; export const RUNNER_POST_OPTIONS = ['대기중인 리뷰', '진행중인 리뷰', '완료된 리뷰']; export const SUPPORTER_POST_OPTIONS = ['신청한 리뷰', '진행중인 리뷰', '완료된 리뷰']; diff --git a/frontend/src/hooks/query/useMyPostList.ts b/frontend/src/hooks/query/useMyPostList.ts index 161753c35..7ec8d7b91 100644 --- a/frontend/src/hooks/query/useMyPostList.ts +++ b/frontend/src/hooks/query/useMyPostList.ts @@ -28,16 +28,16 @@ export const useMyPostList = (isRunner: boolean, reviewStatus?: ReviewStatus) => queryFn: ({ pageParam }) => { return isRunner - ? getMyRunnerPost(PAGE_SIZE, pageParam, reviewStatus) - : getMySupporterPost(PAGE_SIZE, pageParam, reviewStatus); + ? getMyRunnerPost({ limit: PAGE_SIZE, cursor: pageParam, reviewStatus }) + : getMySupporterPost({ limit: PAGE_SIZE, cursor: pageParam, reviewStatus }); }, - initialPageParam: 1, + initialPageParam: 0, getNextPageParam: (nextPage) => { if (nextPage.pageInfo.isLast) return undefined; - return nextPage.pageInfo.currentPage + 1; + return nextPage.pageInfo.nextCursor; }, select: ({ pages }) => pages.reduce((acc, { data }) => acc.concat(data), []), diff --git a/frontend/src/hooks/query/useOtherSupporterPost.ts b/frontend/src/hooks/query/useOtherSupporterPost.ts index 6fdd34b14..e9f34bf38 100644 --- a/frontend/src/hooks/query/useOtherSupporterPost.ts +++ b/frontend/src/hooks/query/useOtherSupporterPost.ts @@ -2,16 +2,29 @@ import { getOtherSupporterPost } from '@/apis/apis'; import { ERROR_TITLE } from '@/constants/message'; import { ToastContext } from '@/contexts/ToastContext'; import { APIError } from '@/types/error'; -import { GetRunnerPostResponse } from '@/types/runnerPost'; -import { useQuery } from '@tanstack/react-query'; +import { GetRunnerPostResponse, RunnerPost } from '@/types/runnerPost'; +import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { useContext, useEffect } from 'react'; +const PAGE_LIMIT = 10; + export const useOtherSupporterPost = (userId: number) => { const { showErrorToast } = useContext(ToastContext); - const queryResult = useQuery({ + const queryResult = useInfiniteQuery({ queryKey: ['otherSupporterPost', userId], - queryFn: () => getOtherSupporterPost(userId).then((res) => res), + + queryFn: ({ pageParam }) => getOtherSupporterPost({ limit: PAGE_LIMIT, supporterId: userId, cursor: pageParam }), + + initialPageParam: 0, + + getNextPageParam: (nextPage) => { + if (nextPage.pageInfo.isLast) return undefined; + + return nextPage.pageInfo.nextCursor; + }, + + select: ({ pages }) => pages.reduce((acc, { data }) => acc.concat(data), []), }); useEffect(() => { @@ -21,6 +34,7 @@ export const useOtherSupporterPost = (userId: number) => { }, [queryResult.error]); return { + ...queryResult, data: queryResult.data as NonNullable, }; }; diff --git a/frontend/src/hooks/query/useRunnerPostList.ts b/frontend/src/hooks/query/useRunnerPostList.ts index 4faaaa39f..f89fcce52 100644 --- a/frontend/src/hooks/query/useRunnerPostList.ts +++ b/frontend/src/hooks/query/useRunnerPostList.ts @@ -7,26 +7,31 @@ import { useInfiniteQuery } from '@tanstack/react-query'; import { useContext, useEffect } from 'react'; const PAGE_LIMIT = 10; +const DEFAULT_REVIEW_STATUS = 'ALL'; -export const useRunnerPostList = (reviewStatus?: ReviewStatus, tagName?: string) => { +export const useRunnerPostList = (reviewStatus: ReviewStatus | null, tagName?: string) => { const { showErrorToast } = useContext(ToastContext); const queryResult = useInfiniteQuery< GetRunnerPostResponse, APIError, RunnerPost[], - [string, typeof reviewStatus, typeof tagName], + [string, string, typeof tagName], number >({ - queryKey: ['runnerPost', reviewStatus, tagName], - queryFn: ({ pageParam }) => getRunnerPost(PAGE_LIMIT, reviewStatus, pageParam, tagName).then((res) => res), + queryKey: ['runnerPost', reviewStatus ?? DEFAULT_REVIEW_STATUS, tagName], + + queryFn: ({ pageParam }) => + getRunnerPost({ limit: PAGE_LIMIT, reviewStatus, cursor: pageParam, tagName }).then((res) => res), + initialPageParam: 0, + getNextPageParam: (nextPage) => { - if (!nextPage.pageInfo.isLast) { - return nextPage.pageInfo.nextCursor; - } - return undefined; + if (nextPage.pageInfo.isLast) return undefined; + + return nextPage.pageInfo.nextCursor; }, + select: ({ pages }) => pages.reduce((acc, { data }) => acc.concat(data), []), }); diff --git a/frontend/src/mocks/data/myPagePost/myPagePostList.json b/frontend/src/mocks/data/myPagePost/myPagePostList.json index 5e0afa32d..c9e65b75e 100644 --- a/frontend/src/mocks/data/myPagePost/myPagePostList.json +++ b/frontend/src/mocks/data/myPagePost/myPagePostList.json @@ -93,12 +93,7 @@ } ], "pageInfo": { - "isFirst": true, "isLast": false, - "hasNext": true, - "totalPages": 4, - "totalElements": 40, - "currentPage": 1, - "currentSize": 12 + "nextCursor": 1 } } diff --git a/frontend/src/mocks/data/runnerPostList.json b/frontend/src/mocks/data/runnerPostList.json index d9623ece5..405c21836 100644 --- a/frontend/src/mocks/data/runnerPostList.json +++ b/frontend/src/mocks/data/runnerPostList.json @@ -102,13 +102,7 @@ } ], "pageInfo": { - "isFirst": true, "isLast": false, - "hasNext": true, - "totalPages": 4, - "totalElements": 48, - "currentPage": 1, - "currentSize": 10, "nextCursor": 1 } } diff --git a/frontend/src/mocks/data/supporterProfilePost.json b/frontend/src/mocks/data/supporterProfilePost.json index d15989304..518caba79 100644 --- a/frontend/src/mocks/data/supporterProfilePost.json +++ b/frontend/src/mocks/data/supporterProfilePost.json @@ -27,5 +27,9 @@ "applicantCount": 4, "reviewStatus": "DONE" } - ] + ], + "pageInfo": { + "isLast": false, + "nextCursor": 1 + } } diff --git a/frontend/src/mocks/handlers.ts b/frontend/src/mocks/handlers.ts index 37bf18ab3..8d730b849 100644 --- a/frontend/src/mocks/handlers.ts +++ b/frontend/src/mocks/handlers.ts @@ -21,6 +21,15 @@ export const handlers = [ const limit = req.url.searchParams.get('limit'); const cursor = req.url.searchParams.get('cursor'); + if (!reviewStatus) { + return res( + ctx.delay(300), + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(runnerPostList), + ); + } + if (!limit) return res( ctx.status(400), @@ -231,9 +240,10 @@ const handleRequest = ( ctx: RestContext, ) => { const reviewStatus = req.url.searchParams.get('reviewStatus'); - const page = req.url.searchParams.get('page'); + const limit = req.url.searchParams.get('limit'); + const cursor = req.url.searchParams.get('cursor'); - if (!reviewStatus || !page) + if (!reviewStatus || !limit) return res( ctx.status(400), ctx.set('Content-Type', 'application/json'), @@ -245,11 +255,12 @@ const handleRequest = ( if (reviewStatus) { CopiedMyPagePostList.data.forEach((post, idx) => { post.runnerPostId = Date.now() + idx; - post.title = `${post.title} (${page})`; + post.title = `${post.title} (${cursor})`; post.reviewStatus = reviewStatus; }); - CopiedMyPagePostList.pageInfo.currentPage = Number(page); + CopiedMyPagePostList.pageInfo.nextCursor = Number(cursor) + 1; + if (Number(cursor) === 3) CopiedMyPagePostList.pageInfo.isLast = true; return res( ctx.delay(300), diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/pages/MainPage.tsx index 0bd419d8c..fb7b8f487 100644 --- a/frontend/src/pages/MainPage.tsx +++ b/frontend/src/pages/MainPage.tsx @@ -8,7 +8,7 @@ import { usePageRouter } from '@/hooks/usePageRouter'; import { useRunnerPostList } from '@/hooks/query/useRunnerPostList'; import useViewport from '@/hooks/useViewport'; import Layout from '@/layout/Layout'; -import { ReviewStatus } from '@/types/runnerPost'; +import { ReviewStatus, ReviewStatusFilter } from '@/types/runnerPost'; import React, { useContext, useState } from 'react'; import { styled } from 'styled-components'; import { isLogin } from '@/apis/auth'; @@ -20,7 +20,7 @@ const MainPage = () => { const { isMobile } = useViewport(); const [enteredTag, setEnteredTag] = useState(''); - const [reviewStatus, setReviewStatus] = useState('NOT_STARTED'); + const [reviewStatus, setReviewStatus] = useState(null); const { data: runnerPostList, hasNextPage, fetchNextPage } = useRunnerPostList(reviewStatus, enteredTag); @@ -49,7 +49,7 @@ const MainPage = () => { diff --git a/frontend/src/pages/SupporterProfilePage.tsx b/frontend/src/pages/SupporterProfilePage.tsx index 98f2445be..d2acfa557 100644 --- a/frontend/src/pages/SupporterProfilePage.tsx +++ b/frontend/src/pages/SupporterProfilePage.tsx @@ -20,8 +20,11 @@ const SupporterProfilePage = () => { const { isMobile } = useViewport(); const { data: supporterProfile } = useOtherSupporterProfile(Number(supporterId)); - const { data: supporterProfilePost } = useOtherSupporterPost(Number(supporterId)); + const { data: supporterProfilePost, hasNextPage, fetchNextPage } = useOtherSupporterPost(Number(supporterId)); + const handleClickMoreButton = () => { + fetchNextPage(); + }; return ( @@ -57,13 +60,26 @@ const SupporterProfilePage = () => { 완료된 리뷰 - {supporterProfilePost?.data?.length} + {/* {supporterProfilePost?.length} */} - {supporterProfilePost?.data?.map((runnerPostData) => ( + {supporterProfilePost?.map((runnerPostData) => ( ))} + + {hasNextPage && ( + + )} + ); }; @@ -242,4 +258,12 @@ const S = { font-size: 28px; } `, + + MoreButtonWrapper: styled.div` + max-width: 1200px; + min-width: 340px; + width: 100%; + margin-top: 30px; + margin-bottom: 20px; + `, }; diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index 2cad560dc..409983bc2 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -1 +1,6 @@ export type Method = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'; + +export interface pageParamsRequest { + limit: number; + cursor?: number; +} diff --git a/frontend/src/types/myPage.ts b/frontend/src/types/myPage.ts index 57856affe..0940b2400 100644 --- a/frontend/src/types/myPage.ts +++ b/frontend/src/types/myPage.ts @@ -1,3 +1,4 @@ +import { pageParamsRequest } from './api'; import { Profile } from './profile'; import { PageInfo, ReviewStatus } from './runnerPost'; @@ -18,3 +19,9 @@ export interface MyPagePost { reviewStatus: ReviewStatus; supporterId: number; } + +interface requestParams { + reviewStatus?: ReviewStatus; +} + +export interface getMyPostRequestParams extends pageParamsRequest, requestParams {} diff --git a/frontend/src/types/profile.ts b/frontend/src/types/profile.ts index 8fcb028c9..357e0c2cd 100644 --- a/frontend/src/types/profile.ts +++ b/frontend/src/types/profile.ts @@ -1,3 +1,4 @@ +import { pageParamsRequest } from './api'; import { Technic } from './tags'; export interface GetRunnerProfileResponse extends Profile {} @@ -20,3 +21,9 @@ export interface Profile { introduction: string; technicalTags: Technic[]; } + +interface requestParams { + supporterId: number; +} + +export interface getSupporterPostRequestParams extends pageParamsRequest, requestParams {} diff --git a/frontend/src/types/runnerPost.ts b/frontend/src/types/runnerPost.ts index 48d4b23f0..806291cbf 100644 --- a/frontend/src/types/runnerPost.ts +++ b/frontend/src/types/runnerPost.ts @@ -1,4 +1,7 @@ +import { pageParamsRequest } from './api'; + export type ReviewStatus = 'NOT_STARTED' | 'IN_PROGRESS' | 'DONE' | 'OVERDUE'; +export type ReviewStatusFilter = ReviewStatus | 'ALL'; export interface GetRunnerPostResponse { data: RunnerPost[]; @@ -53,12 +56,13 @@ export interface CreateRunnerPostRequest { } export interface PageInfo { - isFirst: boolean; isLast: boolean; - hasNext: boolean; - totalPages: number; - totalElements: number; - currentPage: number; - currentSize: number; nextCursor: number; } + +interface requestParams { + tagName?: string; + reviewStatus: ReviewStatus | null; +} + +export interface getRunnerPostRequestParams extends pageParamsRequest, requestParams {}