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>
     </>
   );