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) => { - {currentView==="전체 캐스크"&&} - {currentView==="인기"&&} + {currentView === "전체 캐스크" && ( + + )} + {currentView === "인기" && } ); 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 ( +
+ {hasResult && + isSuccess && + // 검색결과가 있을시 + data?.pages.map((page) => + page.content.map((post) => ) + )} + {isSuccess && !hasResult && ( + // 검색결과 없을 시 + + no result alert + + )} + {/* 로딩창 */} + {isFetchingNextPage || isLoading ? ( + + ) : ( + // 인터섹션옵저버 + hasNextPage &&
+ )} +
+ ); +} + +export default PopularPostCardList; 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 */ 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 { + currentPage: number; + hasNextPage: boolean; +} + +export const getPopularPostListQueryFn = async ({ + page = 0, + size = 10, + sort, + headers, +}: GetPostListOptions & { + headers?: AxiosRequestConfig["headers"]; +}): Promise => { + const axiosPrivate = useAxiosPrivate(); + const { data } = await axiosPrivate.get<{ data: Pagenated }>( + 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) => + [ + "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;