From e828f46e1608717794a157e3fccf30ac63979f63 Mon Sep 17 00:00:00 2001 From: Jungu Lee <1zzangjun@gmail.com> Date: Fri, 5 Jan 2024 19:17:17 +0900 Subject: [PATCH 1/3] =?UTF-8?q?New=20:=20=EC=9D=B8=EA=B8=B0=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=EC=9D=84=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20Server=20path=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/const/serverPath.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/const/serverPath.ts b/client/src/const/serverPath.ts index 85d9a98..0703103 100644 --- a/client/src/const/serverPath.ts +++ b/client/src/const/serverPath.ts @@ -47,7 +47,13 @@ export const DELETE_COMMENT = (postPk: string, commentPk: string) => /** * 게시물리스트를 받아오거나, 작성하는 Path 버전2 (Breaking Change) */ -export const POST_LIST_V2 = "/posts/v2" as const; +export const POST_LIST_V2 = "/posts/v2"; + +/** + * 게시물리스트를 받아오거나, 작성하는 Path 버전2 (Breaking Change) + */ +export const POPULAR_POST_LIST = "/posts/popular"; + /** * ID(pk) 를 입력받아 해당 포스트를 지우는 URL */ From 42ca266d873a3918588c449513428778f5247f3f Mon Sep 17 00:00:00 2001 From: Jungu Lee <1zzangjun@gmail.com> Date: Fri, 5 Jan 2024 19:17:42 +0900 Subject: [PATCH 2/3] =?UTF-8?q?New=20:=20=EC=9D=B8=EA=B8=B0=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=EC=9D=84=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/post/PopularPostCardList.tsx | 64 +++++++++ .../useGetPopularPostListInfiniteQuery.ts | 127 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 client/src/components/post/PopularPostCardList.tsx create mode 100644 client/src/queries/post/useGetPopularPostListInfiniteQuery.ts diff --git a/client/src/components/post/PopularPostCardList.tsx b/client/src/components/post/PopularPostCardList.tsx new file mode 100644 index 0000000..7d17dc3 --- /dev/null +++ b/client/src/components/post/PopularPostCardList.tsx @@ -0,0 +1,64 @@ +"use client"; + +import PostCard from "@/components/post/PostCard"; +import useGetPopularPostListInfiniteQuery, { + UseGetPopularPostListQueryInterface, +} from "@/queries/post/useGetPopularPostListInfiniteQuery"; +import { useInView } from "react-intersection-observer"; +import { useEffect } from "react"; +import { Stack } from "@mui/material"; +import { useMemo } from "react"; +import Image from "next/image"; +import NoResult from "@/assets/images/noResult.png"; +import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; +import PostCardSkeleton from "./PostCardSkeleton"; + +function PopularPostCardList(props: UseGetPopularPostListQueryInterface) { + const { + data, + fetchNextPage, + isFetchingNextPage, + hasNextPage, + isSuccess, + isLoading, + } = useGetPopularPostListInfiniteQuery({ + ...props, + headers: { Authorization: getTokenFromLocalStorage() }, + }); + + const { ref, inView } = useInView(); + useEffect(() => { + if (hasNextPage && inView) fetchNextPage(); + }, [inView, hasNextPage]); + + const hasResult = useMemo( + () => data && data.pages[0].content.length > 0, + [data] + ); + + return ( + <div> + {hasResult && + isSuccess && + // 검색결과가 있을시 + data?.pages.map((page) => + page.content.map((post) => <PostCard {...post} key={post.postNo} />) + )} + {isSuccess && !hasResult && ( + // 검색결과 없을 시 + <Stack justifyContent="center" alignItems="center" py={8}> + <Image src={NoResult} alt="no result alert" /> + </Stack> + )} + {/* 로딩창 */} + {isFetchingNextPage || isLoading ? ( + <PostCardSkeleton /> + ) : ( + // 인터섹션옵저버 + hasNextPage && <div style={{ height: 60 }} ref={ref}></div> + )} + </div> + ); +} + +export default PopularPostCardList; diff --git a/client/src/queries/post/useGetPopularPostListInfiniteQuery.ts b/client/src/queries/post/useGetPopularPostListInfiniteQuery.ts new file mode 100644 index 0000000..d7daba9 --- /dev/null +++ b/client/src/queries/post/useGetPopularPostListInfiniteQuery.ts @@ -0,0 +1,127 @@ +import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; +import { PostInterface } from "@/types/post/PostInterface"; +import { AxiosRequestConfig } from "axios"; +import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; +import { POPULAR_POST_LIST } from "@/const/serverPath"; +import useAxiosPrivate from "@/hooks/useAxiosPrivate"; +import Pagenated from "@/types/Pagenated"; + +export interface UseGetPopularPostListQueryInterface + extends GetPostListOptions { + initialData?: AugmentedGetPostListResponse; + headers?: AxiosRequestConfig["headers"]; +} + +export const useGetPopularPostListInfiniteQuery = ({ + initialData, + size, + sort, + headers, +}: UseGetPopularPostListQueryInterface) => { + return useInfiniteQuery({ + queryKey: getPopularPostListInfiniteQueryKey.byKeyword({ + sort, + }), + + queryFn: async ({ pageParam = 0 }) => + await getPopularPostListQueryFn({ + page: pageParam, + size, + sort, + headers: headers?.Authorization + ? headers + : { Authorization: getTokenFromLocalStorage() }, + }), + + getNextPageParam: ({ + currentPage, + hasNextPage, + }: AugmentedGetPostListResponse) => + hasNextPage ? currentPage + 1 : undefined, + + getPreviousPageParam: ({ currentPage }: AugmentedGetPostListResponse) => + currentPage > 0 ? currentPage - 1 : undefined, + initialPageParam: 0, + initialData: initialData + ? { pages: [initialData], pageParams: [0] } + : undefined, + }); +}; +/** + * 포스트리스트를 받아올 때 Query string으로 사용되는 값 + */ +export interface GetPostListOptions { + page?: number; + size?: number; + sort?: string; +} +/** + * 서버응답값 + 무한스크롤을 위해 증강된 값 + */ +export interface AugmentedGetPostListResponse extends Pagenated<PostInterface> { + currentPage: number; + hasNextPage: boolean; +} + +export const getPopularPostListQueryFn = async ({ + page = 0, + size = 10, + sort, + headers, +}: GetPostListOptions & { + headers?: AxiosRequestConfig<any>["headers"]; +}): Promise<AugmentedGetPostListResponse> => { + const axiosPrivate = useAxiosPrivate(); + const { data } = await axiosPrivate.get<{ data: Pagenated<PostInterface> }>( + POPULAR_POST_LIST, + { + baseURL: process.env.NEXT_PUBLIC_BASE_URL, + params: { + page, + size, + sort: sort ?? "lastModifiedDate,desc", + }, + headers, + } + ); + return { + ...data.data, + currentPage: page, + hasNextPage: data.data.totalElements / ((page + 1) * size) > 1, + }; +}; + +export interface PopularPostListInfiniteQueryKey { + keyword?: string; + userNo?: string; + sort?: string; +} + +export const getPopularPostListInfiniteQueryKey = { + all: ["popular_posts"] as const, + byKeyword: ({ sort }: Omit<GetPostListOptions, "page" | "size">) => + [ + "popular_posts", + { + sort, + }, + ] as const, +}; + +/** + * 모든 포스트리스트 쿼리를 Invalidate 하는 Hooks + * @returns Invalidate 함수 + */ +export const useInvalidatePopularPostList = () => { + /** + * 모든 포스트리스트 쿼리를 Invalidate 하는함수 + */ + const queryClinet = useQueryClient(); + return () => { + queryClinet.invalidateQueries({ + queryKey: getPopularPostListInfiniteQueryKey.all, + }); + }; +}; + +export default useGetPopularPostListInfiniteQuery; From ab021b3fc47fdecbb00dbe6c8248b73a91a8dbb1 Mon Sep 17 00:00:00 2001 From: Jungu Lee <1zzangjun@gmail.com> Date: Fri, 5 Jan 2024 19:17:53 +0900 Subject: [PATCH 3/3] =?UTF-8?q?New=20:=20=EC=9D=B8=EA=B8=B0=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/post/MainPagePostList.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/src/components/post/MainPagePostList.tsx b/client/src/components/post/MainPagePostList.tsx index 1d49b32..b770429 100644 --- a/client/src/components/post/MainPagePostList.tsx +++ b/client/src/components/post/MainPagePostList.tsx @@ -5,6 +5,7 @@ import PostCardList from "@/components/post/PostCardList"; import CustomContainer from "@/components/layout/CustomContainer"; import { useState } from "react"; import CustomToggleButtonGroup from "@/components/CustomToggleButtonGroup"; +import PopularPostCardList from "./PopularPostCardList"; type Props = { initialData: AugmentedGetPostListResponse; @@ -19,11 +20,13 @@ const MainPagePostList = ({ initialData }: Props) => { <CustomToggleButtonGroup value={selectableList} onChange={setCurrentView} - sx={{ position: "fixed", top: 0, left: 0, right: 0,zIndex:1 }} + sx={{ position: "fixed", top: 0, left: 0, right: 0, zIndex: 1 }} /> <CustomContainer mt={5}> - {currentView==="전체 캐스크"&&<PostCardList initialData={initialData} />} - {currentView==="인기"&&<PostCardList sort="likeCount"/>} + {currentView === "전체 캐스크" && ( + <PostCardList initialData={initialData} /> + )} + {currentView === "인기" && <PopularPostCardList />} </CustomContainer> </> );