diff --git a/apps/web/src/apis/community/api.ts b/apps/web/src/apis/community/api.ts index 52e7ba6d..1bd84cf7 100644 --- a/apps/web/src/apis/community/api.ts +++ b/apps/web/src/apis/community/api.ts @@ -12,11 +12,7 @@ import type { } from "@/types/community"; import { axiosInstance, publicAxiosInstance } from "@/utils/axiosInstance"; -// QueryKeys for community domain -export const CommunityQueryKeys = { - posts: "posts", - postList: "postList1", // 기존 api/boards와 동일한 키 유지 -} as const; +export { CommunityQueryKeys } from "./queryKeys"; export interface BoardListResponse { 0: string; diff --git a/apps/web/src/apis/community/deletePost.ts b/apps/web/src/apis/community/deletePost.ts index e7f42384..01ed96b1 100644 --- a/apps/web/src/apis/community/deletePost.ts +++ b/apps/web/src/apis/community/deletePost.ts @@ -47,6 +47,7 @@ const useDeletePost = () => { // 'posts' 쿼리 키를 가진 모든 쿼리를 무효화하여 // 게시글 목록을 다시 불러오도록 합니다. queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] }); + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.postList] }); // ISR 페이지 revalidate if (variables.boardCode && accessToken) { diff --git a/apps/web/src/apis/community/getPostList.ts b/apps/web/src/apis/community/getPostList.ts index 28885d0a..61a40664 100644 --- a/apps/web/src/apis/community/getPostList.ts +++ b/apps/web/src/apis/community/getPostList.ts @@ -1,27 +1,34 @@ -import { useQuery } from "@tanstack/react-query"; +import { queryOptions, useQuery } from "@tanstack/react-query"; -import { CommunityQueryKeys, communityApi } from "./api"; +import { communityApi } from "./api"; +import { + COMMUNITY_POST_LIST_GC_TIME, + COMMUNITY_POST_LIST_STALE_TIME, + communityPostListQueryKey, + sortCommunityPosts, +} from "./postListQuery"; interface UseGetPostListProps { boardCode: string; category?: string | null; } +export const getPostListQueryOptions = ({ boardCode, category = null }: UseGetPostListProps) => + queryOptions({ + queryKey: communityPostListQueryKey(boardCode, category), + queryFn: async () => { + const response = await communityApi.getPostList(boardCode, category); + return sortCommunityPosts(response.data); + }, + staleTime: COMMUNITY_POST_LIST_STALE_TIME, + gcTime: COMMUNITY_POST_LIST_GC_TIME, + }); + /** * @description 게시글 목록 조회 훅 */ const useGetPostList = ({ boardCode, category = null }: UseGetPostListProps) => { - return useQuery({ - queryKey: [CommunityQueryKeys.postList, boardCode, category], - queryFn: () => communityApi.getPostList(boardCode, category), - staleTime: Infinity, - gcTime: 1000 * 60 * 30, // 30분 - select: (response) => { - return [...response.data].sort((a, b) => { - return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); - }); - }, - }); + return useQuery(getPostListQueryOptions({ boardCode, category })); }; export default useGetPostList; diff --git a/apps/web/src/apis/community/index.ts b/apps/web/src/apis/community/index.ts index d396a31d..38399a20 100644 --- a/apps/web/src/apis/community/index.ts +++ b/apps/web/src/apis/community/index.ts @@ -15,7 +15,7 @@ export { default as useDeletePost } from "./deletePost"; export { default as useGetBoard } from "./getBoard"; export { default as useGetBoardList } from "./getBoardList"; export { default as useGetPostDetail } from "./getPostDetail"; -export { default as useGetPostList } from "./getPostList"; +export { default as useGetPostList, getPostListQueryOptions } from "./getPostList"; export { default as usePatchUpdateComment } from "./patchUpdateComment"; export { default as useUpdatePost } from "./patchUpdatePost"; export { default as useCreateComment } from "./postCreateComment"; diff --git a/apps/web/src/apis/community/patchUpdatePost.ts b/apps/web/src/apis/community/patchUpdatePost.ts index a45e948d..10c3a4ef 100644 --- a/apps/web/src/apis/community/patchUpdatePost.ts +++ b/apps/web/src/apis/community/patchUpdatePost.ts @@ -45,6 +45,7 @@ const useUpdatePost = () => { // 해당 게시글 상세 쿼리와 목록 쿼리를 무효화 queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts, variables.postId] }); queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] }); + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.postList] }); // ISR 페이지 revalidate if (variables.boardCode && accessToken) { diff --git a/apps/web/src/apis/community/postCreatePost.ts b/apps/web/src/apis/community/postCreatePost.ts index aec6291c..41e48b34 100644 --- a/apps/web/src/apis/community/postCreatePost.ts +++ b/apps/web/src/apis/community/postCreatePost.ts @@ -38,6 +38,7 @@ const useCreatePost = () => { onSuccess: async (data) => { // 게시글 목록 쿼리를 무효화하여 최신 목록 반영 queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] }); + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.postList] }); // ISR 페이지 revalidate (사용자 인증 토큰 사용) if (accessToken) { diff --git a/apps/web/src/apis/community/postListQuery.ts b/apps/web/src/apis/community/postListQuery.ts new file mode 100644 index 00000000..e4253c10 --- /dev/null +++ b/apps/web/src/apis/community/postListQuery.ts @@ -0,0 +1,12 @@ +import type { ListPost } from "@/types/community"; +import { CommunityQueryKeys } from "./queryKeys"; + +export const COMMUNITY_INITIAL_CATEGORY = "전체"; +export const COMMUNITY_POST_LIST_STALE_TIME = Infinity; +export const COMMUNITY_POST_LIST_GC_TIME = 1000 * 60 * 30; // 30분 + +export const communityPostListQueryKey = (boardCode: string, category: string | null = null) => + [CommunityQueryKeys.postList, boardCode, category] as const; + +export const sortCommunityPosts = (posts: ListPost[]) => + [...posts].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()); diff --git a/apps/web/src/apis/community/queryKeys.ts b/apps/web/src/apis/community/queryKeys.ts new file mode 100644 index 00000000..a6d3b7e5 --- /dev/null +++ b/apps/web/src/apis/community/queryKeys.ts @@ -0,0 +1,5 @@ +// QueryKeys for community domain +export const CommunityQueryKeys = { + posts: "posts", + postList: "postList1", // 기존 api/boards와 동일한 키 유지 +} as const; diff --git a/apps/web/src/app/community/[boardCode]/CommunityPageContent.tsx b/apps/web/src/app/community/[boardCode]/CommunityPageContent.tsx index 4d0ac283..0474b524 100644 --- a/apps/web/src/app/community/[boardCode]/CommunityPageContent.tsx +++ b/apps/web/src/app/community/[boardCode]/CommunityPageContent.tsx @@ -3,10 +3,12 @@ import { useRouter } from "next/navigation"; import { useMemo, useState } from "react"; import { useGetPostList } from "@/apis/community"; +import { COMMUNITY_INITIAL_CATEGORY } from "@/apis/community/postListQuery"; import ButtonTab from "@/components/ui/ButtonTab"; import { COMMUNITY_BOARDS, COMMUNITY_CATEGORIES } from "@/constants/community"; import useReportedPostsStore from "@/lib/zustand/useReportedPostsStore"; import type { ListPost } from "@/types/community"; +import { CommunityPostListSkeleton } from "./CommunityPageSkeleton"; import CommunityRegionSelector from "./CommunityRegionSelector"; import PostCards from "./PostCards"; import PostWriteButton from "./PostWriteButton"; @@ -23,11 +25,11 @@ interface CommunityPageContentProps { const CommunityPageContent = ({ boardCode }: CommunityPageContentProps) => { const router = useRouter(); - const [category, setCategory] = useState("전체"); + const [category, setCategory] = useState(COMMUNITY_INITIAL_CATEGORY); const reportedPostIds = useReportedPostsStore((state) => state.reportedPostIds); const blockedUserIds = useReportedPostsStore((state) => state.blockedUserIds); - const { data: posts = [] } = useGetPostList({ + const { data: posts = [], isPending } = useGetPostList({ boardCode, category, }); @@ -81,7 +83,7 @@ const CommunityPageContent = ({ boardCode }: CommunityPageContentProps) => { setChoice={setCategory} style={{ padding: "10px 0 10px 18px" }} /> - {} + {isPending ? : } ); diff --git a/apps/web/src/app/community/[boardCode]/CommunityPageSkeleton.tsx b/apps/web/src/app/community/[boardCode]/CommunityPageSkeleton.tsx new file mode 100644 index 00000000..75771526 --- /dev/null +++ b/apps/web/src/app/community/[boardCode]/CommunityPageSkeleton.tsx @@ -0,0 +1,46 @@ +const TAB_SKELETON_WIDTHS = ["w-12", "w-12", "w-12"]; + +export const CommunityPostListSkeleton = ({ itemCount = 5 }: { itemCount?: number }) => ( +