diff --git a/app/(main)/connect/[id]/page.tsx b/app/(main)/connect/[id]/page.tsx index d635b263..ef57412a 100644 --- a/app/(main)/connect/[id]/page.tsx +++ b/app/(main)/connect/[id]/page.tsx @@ -2,7 +2,7 @@ import { getPostDetailServer } from "@/features/connect/apis/getPostDetailServer import { dehydrate, HydrationBoundary } from "@tanstack/react-query"; import { getQueryClient } from "@/libs/getQueryClient"; import PostDetailContainer from "@/features/connect/containers/PostDetailContainer"; -import { connectQueryKeys } from "@/features/connect/queries"; +import { connectQueryKeys } from "@/features/shared/queryKeys/connect"; import QueryErrorBoundary from "@/components/common/QueryErrorBoundary"; import { Metadata } from "next"; diff --git a/app/(main)/connect/edit/[id]/page.tsx b/app/(main)/connect/edit/[id]/page.tsx index 1ae1baf3..83570d4f 100644 --- a/app/(main)/connect/edit/[id]/page.tsx +++ b/app/(main)/connect/edit/[id]/page.tsx @@ -2,7 +2,7 @@ import { dehydrate, HydrationBoundary } from "@tanstack/react-query"; import { getQueryClient } from "@/libs/getQueryClient"; import EditPostContainer from "@/features/connect/containers/EditPostContainer"; import { getPostDetailServer } from "@/features/connect/apis/getPostDetailServer"; -import { connectQueryKeys } from "@/features/connect/queries"; +import { connectQueryKeys } from "@/features/shared/queryKeys/connect"; import QueryErrorBoundary from "@/components/common/QueryErrorBoundary"; import { Metadata } from "next"; diff --git a/app/(main)/connect/page.tsx b/app/(main)/connect/page.tsx index 36a8ea95..92fe978b 100644 --- a/app/(main)/connect/page.tsx +++ b/app/(main)/connect/page.tsx @@ -6,7 +6,7 @@ import { getQueryClient } from "@/libs/getQueryClient"; import { Suspense } from "react"; import { serverFetch } from "@/libs/serverFetch"; import IntroSection from "@/features/connect/components/IntroSection"; -import { connectQueryKeys } from "@/features/connect/queries"; +import { connectQueryKeys } from "@/features/shared/queryKeys/connect"; import QueryErrorBoundary from "@/components/common/QueryErrorBoundary"; import { Metadata } from "next"; @@ -43,7 +43,7 @@ export default async function ConnectPage({ staleTime: 1000 * 60, }), queryClient.prefetchQuery({ - queryKey: connectQueryKeys.hotPosts(), + queryKey: connectQueryKeys.hotPosts, queryFn: async () => { const res = await serverFetch(`/posts?type=best&limit=20`); if (!res.ok) throw new Error("HOT 게시글 조회 실패"); diff --git a/app/(main)/meetup/[meetupId]/page.tsx b/app/(main)/meetup/[meetupId]/page.tsx index 03316fee..41279067 100644 --- a/app/(main)/meetup/[meetupId]/page.tsx +++ b/app/(main)/meetup/[meetupId]/page.tsx @@ -5,11 +5,11 @@ import { getRelatedMeetingsServer, getReviewsServer, } from "@/features/meetupDetail/apis/apis.server"; -import { meetupDetailQueryKeys } from "@/features/meetupDetail/queries"; import MeetupDetailClient from "@/features/meetupDetail/containers/meetupContainer"; import { Metadata } from "next"; import { notFound } from "next/navigation"; import { getQueryClient } from "@/libs/getQueryClient"; +import { meetupDetailQueryKeys } from "@/features/shared/queryKeys/meetupDetail"; interface PageProps { params: Promise<{ meetupId: string }>; @@ -47,18 +47,18 @@ export default async function MeetupDetailPage({ params }: PageProps) { await Promise.all([ queryClient.prefetchQuery({ - queryKey: meetupDetailQueryKeys.meeting(meetingId), + queryKey: meetupDetailQueryKeys.meeting.detail(meetingId), queryFn: () => meeting, staleTime: 1000 * 60 * 5, }), queryClient.prefetchInfiniteQuery({ - queryKey: meetupDetailQueryKeys.participants(meetingId), + queryKey: meetupDetailQueryKeys.participants.detail(meetingId), queryFn: () => getParticipantsServer(meetingId), initialPageParam: undefined, staleTime: 1000 * 60 * 3, }), queryClient.prefetchQuery({ - queryKey: meetupDetailQueryKeys.reviews(meetingId, undefined), + queryKey: meetupDetailQueryKeys.reviews.detail(meetingId, undefined), queryFn: () => getReviewsServer(meetingId), staleTime: 1000 * 60 * 10, }), diff --git a/app/layout.tsx b/app/layout.tsx index 5edf4701..84826b36 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -11,6 +11,7 @@ import { getMeServer } from "@/features/auth/queries.server"; import { cookies } from "next/headers"; import { getQueryClient } from "@/libs/getQueryClient"; import Script from "next/script"; +import { authQueryKeys } from "@/features/shared/queryKeys/auth"; const pretendard = localFont({ src: "../public/assets/fonts/PretendardVariable.woff2", @@ -101,7 +102,7 @@ export default async function RootLayout({ if (accessToken) { await queryClient .prefetchQuery({ - queryKey: ["me"], + queryKey: authQueryKeys.me, queryFn: getMeServer, }) .catch(() => {}); diff --git a/features/auth/mutations.ts b/features/auth/mutations.ts index f8ebc75c..feaff47f 100644 --- a/features/auth/mutations.ts +++ b/features/auth/mutations.ts @@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { postLogin, postSignUp, postLogout, postOAuthLogin } from "@/features/auth/apis"; import { useToast } from "@/providers/toast-provider"; import { useRouter } from "next/navigation"; +import { authQueryKeys } from "@/features/shared/queryKeys/auth"; export function useLogin(onSuccess: () => void) { const queryClient = useQueryClient(); @@ -11,7 +12,7 @@ export function useLogin(onSuccess: () => void) { return useMutation({ mutationFn: postLogin, onSuccess: (data) => { - queryClient.setQueryData(["me"], data.user); + queryClient.setQueryData(authQueryKeys.me, data.user); handleShowToast({ message: "로그인이 완료됐습니다.", status: "success" }); onSuccess(); router.refresh(); @@ -34,7 +35,7 @@ export function useSignUp(onSuccess: () => void, onAutoLoginFail?: () => void) { email: variables.email, password: variables.password, }); - queryClient.setQueryData(["me"], loginResult.user); + queryClient.setQueryData(authQueryKeys.me, loginResult.user); handleShowToast({ message: "회원가입이 완료됐습니다.", status: "success" }); onSuccess(); @@ -81,7 +82,7 @@ export function useOAuthLogin(onSuccess: () => void) { mutationFn: ({ provider, token }: { provider: "google" | "kakao"; token: string }) => postOAuthLogin(provider, token), onSuccess: (data) => { - queryClient.setQueryData(["me"], data.user); + queryClient.setQueryData(authQueryKeys.me, data.user); handleShowToast({ message: "로그인이 완료됐습니다.", status: "success" }); onSuccess(); router.refresh(); diff --git a/features/auth/queries.ts b/features/auth/queries.ts index eae9620b..7f81ddbf 100644 --- a/features/auth/queries.ts +++ b/features/auth/queries.ts @@ -1,6 +1,8 @@ import { useQuery } from "@tanstack/react-query"; import { clientFetch } from "@/libs/clientFetch"; import { User } from "@/features/auth/types"; +import { authQueryKeys } from "@/features/shared/queryKeys/auth"; + async function getMe(): Promise { const response = await clientFetch("/users/me"); @@ -11,7 +13,7 @@ async function getMe(): Promise { export function useGetMe() { return useQuery({ - queryKey: ["me"], + queryKey: authQueryKeys.me, queryFn: getMe, retry: false, staleTime: 1000 * 60 * 5, diff --git a/features/connect/components/Banner/index.stories.tsx b/features/connect/components/Banner/index.stories.tsx index 0fa6f933..3a33bb31 100644 --- a/features/connect/components/Banner/index.stories.tsx +++ b/features/connect/components/Banner/index.stories.tsx @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/nextjs-vite"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Suspense } from "react"; import ConnectBanner from "."; -import { connectQueryKeys } from "@/features/connect/queries"; +import { connectQueryKeys } from "@/features/shared/queryKeys/connect"; const MOCK_HOT_POSTS = { data: [ @@ -49,7 +49,7 @@ export const Default: Story = { decorators: [ (Story) => { const queryClient = createQueryClient(); - queryClient.setQueryData(connectQueryKeys.hotPosts(), MOCK_HOT_POSTS); + queryClient.setQueryData(connectQueryKeys.hotPosts, MOCK_HOT_POSTS); return ( @@ -69,7 +69,7 @@ export const Empty: Story = { decorators: [ (Story) => { const queryClient = createQueryClient(); - queryClient.setQueryData(connectQueryKeys.hotPosts(), { data: [] }); + queryClient.setQueryData(connectQueryKeys.hotPosts, { data: [] }); return ( diff --git a/features/connect/mutations.ts b/features/connect/mutations.ts index 90aaaf06..2b797dc6 100644 --- a/features/connect/mutations.ts +++ b/features/connect/mutations.ts @@ -14,14 +14,13 @@ import { createComment } from "@/features/connect/apis/createComment"; import { updateComment } from "@/features/connect/apis/updateComment"; import { useToast } from "@/providers/toast-provider"; import { deleteComment } from "@/features/connect/apis/deleteComment"; -import { connectQueryKeys } from "@/features/connect/queries"; -import { headerQueryKeys } from "@/features/header/queries"; +import { connectQueryKeys } from "@/features/shared/queryKeys/connect"; import { useUser } from "@/hooks/useUser"; +import { headerQueryKeys } from "@/features/shared/queryKeys/header"; // 댓글/좋아요 뮤테이션 후 공통으로 무효화할 헤더 관련 쿼리 function invalidateHeaderQueries(queryClient: ReturnType) { - queryClient.invalidateQueries({ queryKey: headerQueryKeys.notifications }); // 알림 목록 - queryClient.invalidateQueries({ queryKey: headerQueryKeys.notificationsCount }); // 읽지 않은 알림 수 + queryClient.invalidateQueries({ queryKey: headerQueryKeys.notifications.all }); // 알림 목록 } // 게시글 좋아요 토글 (Optimistic Update) @@ -58,7 +57,7 @@ export function useToggleConnectLike(postId: number) { onSuccess: () => { // lists만 stale 표시 → 목록으로 돌아갈 때 자동 갱신 queryClient.invalidateQueries({ - queryKey: connectQueryKeys.lists(), + queryKey: connectQueryKeys.lists, refetchType: "none", // 즉시 refetch 안 함 }); }, @@ -82,7 +81,7 @@ export function useCreatePost() { mutationFn: createPost, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: connectQueryKeys.lists() }); // 목록 전체 무효화 + queryClient.invalidateQueries({ queryKey: connectQueryKeys.lists }); // 목록 전체 무효화 }, onError: (err) => { @@ -102,7 +101,7 @@ export function useDeletePost(postId: number) { mutationFn: () => deletePost(postId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: connectQueryKeys.lists() }); // 목록 전체 무효화 + queryClient.invalidateQueries({ queryKey: connectQueryKeys.lists }); // 목록 전체 무효화 router.replace("/connect?deleted=true"); }, @@ -122,7 +121,7 @@ export function useUpdatePost(postId: number) { mutationFn: (data: { title: string; content: string }) => updatePost(postId, data), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: connectQueryKeys.lists() }); // 목록 전체 + queryClient.invalidateQueries({ queryKey: connectQueryKeys.lists }); // 목록 전체 }, onError: (err) => { diff --git a/features/connect/queries.ts b/features/connect/queries.ts index 128e4eb7..af9960f7 100644 --- a/features/connect/queries.ts +++ b/features/connect/queries.ts @@ -2,27 +2,7 @@ import { fetchPostsClient } from "@/features/connect/apis/fetchPostsClient"; import { getPostDetailClient } from "@/features/connect/apis/getPostDetailClient"; import { useSuspenseQuery, useQuery, keepPreviousData } from "@tanstack/react-query"; import { getUserProfile } from "@/apis/UserProfile"; - -export const connectQueryKeys = { - // 게시글 관련 전체 키 (invalidateQueries 범위 제어용) - all: () => ["connect"] as const, - - // 게시글 목록 관련 키 - lists: () => [...connectQueryKeys.all(), "list"] as const, - - // 게시글 목록 필터 조합 키 - list: (page: number, sortBy: string, limit: number, keyword: string) => - [...connectQueryKeys.lists(), { page, sortBy, limit, keyword }] as const, - - // 인기 게시글 키 - hotPosts: () => [...connectQueryKeys.all(), "hotPosts"] as const, - - // 게시글 상세 키 - detail: (postId: number) => [...connectQueryKeys.all(), "detail", postId] as const, - - // 유저 프로필 키 - userProfile: (userId: number) => ["user", "profile", userId] as const, -}; +import { connectQueryKeys } from "@/features/shared/queryKeys/connect"; // 게시글 목록 조회 (페이지네이션 + 정렬 + 검색) export function useGetPosts({ @@ -54,7 +34,7 @@ export function useGetPosts({ // 인기 게시글 목록 조회 (상위 20개, Suspense 기반) export function useGetHotPosts() { return useSuspenseQuery({ - queryKey: connectQueryKeys.hotPosts(), + queryKey: connectQueryKeys.hotPosts, queryFn: () => fetchPostsClient({ type: "best", limit: 20 }), staleTime: 1000 * 60 * 5, retry: 1, diff --git a/features/favorites/mutations.ts b/features/favorites/mutations.ts index 79ed4a59..2ba80417 100644 --- a/features/favorites/mutations.ts +++ b/features/favorites/mutations.ts @@ -5,22 +5,17 @@ import { postMeetingsFavorite, postMeetingsJoin, } from "@/apis/meetings"; -import { headerQueryKeys } from "@/features/header/queries"; -import { meetupDetailQueryKeys } from "@/features/meetupDetail/queries"; -import { mypageQueryKeys } from "@/features/mypage/queries"; -import { MeetupListRequest } from "../meetup/types"; -import { queryKeys } from "./queries/queryKeys"; +import { meetupDetailQueryKeys } from "@/features/shared/queryKeys/meetupDetail"; +import { headerQueryKeys } from "@/features/shared/queryKeys/header"; +import { mypageQueryKeys } from "@/features/shared/queryKeys/mypage"; +import { meetupQueryKeys } from "@/features/shared/queryKeys/meetup"; +import { favoritesQueryKeys } from "../shared/queryKeys/favorites"; type MutationCallbacks = Omit< UseMutationOptions, "mutationKey" | "mutationFn" >; -export const meetupQueryKeys = { - list: ["meetup", "list"] as const, - listWithParams: (params: MeetupListRequest) => [...meetupQueryKeys.list, params] as const, -}; - export const meetupMutationKeys = { postMeetup: ["meetup", "post"] as const, uploadImage: ["meetup", "image", "upload"] as const, @@ -33,7 +28,7 @@ export const meetupMutationKeys = { async function invalidateMeetupAndFavoritesQueries(queryClient: ReturnType) { await Promise.all([ queryClient.invalidateQueries({ - queryKey: queryKeys.favorites.all, + queryKey: favoritesQueryKeys.favorites.all, }), queryClient.invalidateQueries({ queryKey: meetupQueryKeys.list, @@ -51,10 +46,10 @@ async function invalidateFavoriteRelatedQueries( queryKey: headerQueryKeys.favorites, }), queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.meeting(meetingId), + queryKey: meetupDetailQueryKeys.meeting.detail(meetingId), }), queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.related.all(), + queryKey: meetupDetailQueryKeys.related.all, }), ]); } @@ -66,22 +61,16 @@ async function invalidateJoinRelatedQueries( await Promise.all([ invalidateMeetupAndFavoritesQueries(queryClient), queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.meeting(meetingId), - }), - queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.participants(meetingId), - }), - queryClient.invalidateQueries({ - queryKey: mypageQueryKeys.meetups, + queryKey: meetupDetailQueryKeys.meeting.detail(meetingId), }), queryClient.invalidateQueries({ - queryKey: mypageQueryKeys.created, + queryKey: meetupDetailQueryKeys.participants.detail(meetingId), }), queryClient.invalidateQueries({ - queryKey: headerQueryKeys.notifications, + queryKey: mypageQueryKeys.meetups.all, }), queryClient.invalidateQueries({ - queryKey: headerQueryKeys.notificationsCount, + queryKey: headerQueryKeys.notifications.all, }), ]); } diff --git a/features/favorites/queries/queryOptions.ts b/features/favorites/queries/queryOptions.ts index 00ee8595..ee8c272a 100644 --- a/features/favorites/queries/queryOptions.ts +++ b/features/favorites/queries/queryOptions.ts @@ -1,5 +1,5 @@ import { FavoritesListRequest, FavoritesListResponse } from "../types"; -import { queryKeys } from "./queryKeys"; +import { favoritesQueryKeys } from "@/features/shared/queryKeys/favorites"; export function favoritesInfiniteOptions( params: FavoritesListRequest, @@ -9,7 +9,7 @@ export function favoritesInfiniteOptions( const querykeyParams = { ...rest }; return { - queryKey: queryKeys.favorites.list(querykeyParams), + queryKey: favoritesQueryKeys.favorites.list(querykeyParams), queryFn: ({ pageParam }: { pageParam: string | undefined }) => getFavorites({ ...params, diff --git a/features/header/mutations.ts b/features/header/mutations.ts index 438823f0..bf314a4d 100644 --- a/features/header/mutations.ts +++ b/features/header/mutations.ts @@ -6,10 +6,10 @@ import { putNotificationsReadAll, } from "./apis"; import { useToast } from "@/providers/toast-provider"; -import { headerQueryKeys } from "./queries"; import { CursorPageResponse, NotificationCardList } from "./types"; +import { headerQueryKeys } from "@/features/shared/queryKeys/header"; -const notificationQueryKey = headerQueryKeys.notification.all; +const notificationQueryKey = headerQueryKeys.notifications.all; export function usePutNotificationsReadAll() { const queryClient = useQueryClient(); diff --git a/features/header/queries.ts b/features/header/queries.ts index bb4d7d30..0efaa28c 100644 --- a/features/header/queries.ts +++ b/features/header/queries.ts @@ -1,21 +1,7 @@ import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import { getFavoritesCount, getNotifications, getNotificationUnreadCount } from "./apis"; import { useUser } from "@/hooks/useUser"; - -const HEADER_QUERY_BASE_KEY = ["header"] as const; -export const headerQueryKeys = { - // 찜 개수 - all: HEADER_QUERY_BASE_KEY, - favorites: [...HEADER_QUERY_BASE_KEY, "favorites"] as const, - notification: { - all: [...HEADER_QUERY_BASE_KEY, "notifications"] as const, - count: [...HEADER_QUERY_BASE_KEY, "notifications", "count"] as const, - }, - - // @TODO 삭제예정 legacy - notifications: ["header", "notifications"] as const, - notificationsCount: ["header", "notifications", "count"] as const, -} as const; +import { headerQueryKeys } from "@/features/shared/queryKeys/header"; // 찜한 갯수 export function useGetFavoritesCount() { @@ -33,7 +19,7 @@ export function useGetNotifications() { const { user } = useUser(); return useInfiniteQuery({ - queryKey: headerQueryKeys.notifications, + queryKey: headerQueryKeys.notifications.all, queryFn: ({ pageParam }) => getNotifications(pageParam), getNextPageParam: (lastPage) => lastPage.hasMore ? (lastPage.nextCursor ?? undefined) : undefined, @@ -48,7 +34,7 @@ export function useGetNotificationsCount() { const { user } = useUser(); return useQuery({ - queryKey: headerQueryKeys.notificationsCount, + queryKey: headerQueryKeys.notifications.count, queryFn: getNotificationUnreadCount, staleTime: 1000 * 60 * 5, enabled: !!user, diff --git a/features/meetup/components/FileField/index.tsx b/features/meetup/components/FileField/index.tsx index 3f00a3aa..882c955d 100644 --- a/features/meetup/components/FileField/index.tsx +++ b/features/meetup/components/FileField/index.tsx @@ -1,11 +1,11 @@ "use client"; import { useRef } from "react"; -import InputFile, { type InputFileHandle } from "@/components/ui/Inputs/InputFile"; -import { useToast } from "@/providers/toast-provider"; -import type { UploadImageFn } from "@/apis/images"; import { useMutation } from "@tanstack/react-query"; -import { meetupMutationKeys } from "../../queries"; +import type { UploadImageFn } from "@/apis/images"; +import { useToast } from "@/providers/toast-provider"; +import InputFile, { type InputFileHandle } from "@/components/ui/Inputs/InputFile"; +import { meetupMutationKeys } from "@/features/shared/queryKeys/meetup"; interface FileFieldProps { /** 기본 이미지 */ diff --git a/features/meetup/create/components/CreateForm/index.tsx b/features/meetup/create/components/CreateForm/index.tsx index 786d9a3d..2dcf81d8 100644 --- a/features/meetup/create/components/CreateForm/index.tsx +++ b/features/meetup/create/components/CreateForm/index.tsx @@ -5,9 +5,8 @@ import { useQueryClient } from "@tanstack/react-query"; import useToggle from "@/hooks/useToggle"; import { useToast } from "@/providers/toast-provider"; import { cn } from "@/utils/cn"; -import { mypageQueryKeys } from "@/features/mypage/queries"; import { Modal } from "@/components/ui/Modals"; -import { meetupQueryKeys, usePostMeetup, useUploadMeetupImage } from "../../../queries"; +import { usePostMeetup, useUploadMeetupImage } from "../../../queries"; import { MeetupCreateRequest } from "../../../types"; import { getKakaoPlace } from "../../../apis"; import { modalSizeStyle } from "../../../styles"; @@ -21,6 +20,8 @@ import StepTypeSelect from "../StepTypeSelect"; import StepInfo from "../StepInfo"; import StepSchedule from "../StepSchedule"; import StepDesc from "../StepDesc"; +import { mypageQueryKeys } from "@/features/shared/queryKeys/mypage"; +import { meetupQueryKeys } from "@/features/shared/queryKeys/meetup"; export type OnSubmit = (data: MeetupCreateRequest) => Promise; export type OnSuccess = (id: number) => void; @@ -79,8 +80,7 @@ function CreateForm({ onClose, onSuccess, footerClassName }: CreateFormProps) { const postMeetupMutation = usePostMeetup({ onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: meetupQueryKeys.list }); // 모임 목록 - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups }); // 참여한 모임 목록 - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.created }); // 만든 모임 목록 + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups.all }); // 참여한 / 만든 모임 목록 onSuccess(data.id); }, onError: (error) => { diff --git a/features/meetup/list/hooks.ts b/features/meetup/list/hooks.ts index bfe5ec10..371f06b7 100644 --- a/features/meetup/list/hooks.ts +++ b/features/meetup/list/hooks.ts @@ -1,12 +1,12 @@ import { useRef } from "react"; import { InfiniteData, QueryKey, useQueryClient } from "@tanstack/react-query"; import { useToast } from "@/providers/toast-provider"; -import { meetupQueryKeys } from "../queries"; -import { meetupDetailQueryKeys } from "@/features/meetupDetail/queries"; -import { mypageQueryKeys } from "@/features/mypage/queries"; -import { headerQueryKeys } from "@/features/header/queries"; -import { queryKeys } from "@/features/favorites/queries/queryKeys"; import type { MeetupListResponse } from "../types"; +import { meetupDetailQueryKeys } from "@/features/shared/queryKeys/meetupDetail"; +import { headerQueryKeys } from "@/features/shared/queryKeys/header"; +import { mypageQueryKeys } from "@/features/shared/queryKeys/mypage"; +import { meetupQueryKeys } from "@/features/shared/queryKeys/meetup"; +import { favoritesQueryKeys } from "@/features/shared/queryKeys/favorites"; export function useMeetupToggle(meetingId: number, field: "isJoined" | "isFavorited") { const queryClient = useQueryClient(); @@ -56,19 +56,19 @@ export function useMeetupToggle(meetingId: number, field: "isJoined" | "isFavori handleShowToast({ message, status: "success" }); // 목록 쿼리를 포함한 연관 쿼리 무효화 queryClient.invalidateQueries({ queryKey: meetupQueryKeys.list, refetchType: "none" }); // 모임 목록 stale 처리(refetch X) - queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting(meetingId) }); // 해당 모임 상세 - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups }); // 참여한 모임 목록 - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.created }); // 만든 모임 목록(주최자) + queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting.detail(meetingId) }); // 해당 모임 상세 + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups.all }); // 참여한 /만든 모임 목록 if (field === "isFavorited") { queryClient.invalidateQueries({ queryKey: headerQueryKeys.favorites }); // 찜 개수 - queryClient.invalidateQueries({ queryKey: queryKeys.favorites.all }); // 찜한 목록 + queryClient.invalidateQueries({ queryKey: favoritesQueryKeys.favorites.all }); // 찜한 목록 } if (field === "isJoined") { - queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.participants(meetingId) }); // 해당 모임 참여자 + queryClient.invalidateQueries({ + queryKey: meetupDetailQueryKeys.participants.detail(meetingId), + }); // 해당 모임 참여자 // 참여 가능 인원이 초과되어 해당 모임이 확정되는 경우 - queryClient.invalidateQueries({ queryKey: headerQueryKeys.notifications }); // 알림 목록 - queryClient.invalidateQueries({ queryKey: headerQueryKeys.notificationsCount }); // 알림 개수 + queryClient.invalidateQueries({ queryKey: headerQueryKeys.notifications.all }); // 알림 목록 } } diff --git a/features/meetup/queries.ts b/features/meetup/queries.ts index dce56a8f..c8dc93c7 100644 --- a/features/meetup/queries.ts +++ b/features/meetup/queries.ts @@ -5,7 +5,7 @@ import { UseMutationOptions, useQueryClient, } from "@tanstack/react-query"; -import type { MeetupCreateRequest, MeetupItemResponse, MeetupListRequest } from "./types"; +import type { MeetupCreateRequest, MeetupItemResponse } from "./types"; import { getMeetups, postMeetup } from "./apis"; import { deleteMeetingsFavorite, @@ -27,26 +27,13 @@ import { import { QUERY_KEYS } from "./list/constants"; import { useQueryParams } from "@/hooks/useQueryParams"; import { useEffect } from "react"; +import { meetupMutationKeys, meetupQueryKeys } from "@/features/shared/queryKeys/meetup"; type MutationCallbacks = Omit< UseMutationOptions, "mutationKey" | "mutationFn" >; -export const meetupQueryKeys = { - list: ["meetup", "list"] as const, - listWithParams: (params: MeetupListRequest) => [...meetupQueryKeys.list, params] as const, -}; - -export const meetupMutationKeys = { - postMeetup: ["meetup", "post"] as const, - uploadImage: ["meetup", "image", "upload"] as const, - postFavorite: ["meetings", "favorite", "post"] as const, - deleteFavorite: ["meetings", "favorite", "delete"] as const, - postJoin: ["meetings", "join", "post"] as const, - deleteJoin: ["meetings", "join", "delete"] as const, -}; - /** 모임 목록 조회 */ export function useGetMeetups(size: number) { const queryClient = useQueryClient(); diff --git a/features/meetupDetail/keys.ts b/features/meetupDetail/keys.ts deleted file mode 100644 index fd95c0c2..00000000 --- a/features/meetupDetail/keys.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * meetupDetail query/mutation Key 관리 파일 - * - * 현재는 기존 queries.ts & mutations.tsDML 의 key 선언과 병행 유지합니다. - * 추후 팀 전체 key 통합 시, ex) queryKeys.ts로 이동하고, - * 기존 선언 및 import 경로를 일괄 변경합니다. - * */ - -export const meetupDetailQueryKeys = { - meeting: (meetingId: number) => ["meetupDetail", "meeting", meetingId] as const, - participants: (meetingId: number) => ["meetupDetail", "participants", meetingId] as const, - reviews: (meetingId: number, cursor?: string) => - ["meetupDetail", "reviews", meetingId, cursor] as const, - related: { - all: () => ["meetupDetail", "related"] as const, - detail: (meetingId: number, region: string, type: string) => - ["meetupDetail", "related", meetingId, region, type] as const, - }, -}; - -export const meetupDetailMutationKeys = { - join: ["meetupDetail", "join", "post"] as const, - cancelJoin: ["meetupDetail", "join", "delete"] as const, - favorite: ["meetupDetail", "favorite"] as const, - editMeeting: ["meetupDetail", "meeting", "patch"] as const, - deleteMeeting: ["meetupDetail", "meeting", "delete"] as const, - editReview: ["meetupDetail", "review", "patch"] as const, - deleteReview: ["meetupDetail", "review", "delete"] as const, -}; diff --git a/features/meetupDetail/mutations.ts b/features/meetupDetail/mutations.ts index 5e9a8b9e..d45ff827 100644 --- a/features/meetupDetail/mutations.ts +++ b/features/meetupDetail/mutations.ts @@ -10,13 +10,14 @@ import { postFavorite, postJoin, } from "@/features/meetupDetail/apis/apis"; -import { meetupDetailQueryKeys } from "./queries"; import { MeetupEditData } from "@/features/meetupDetail/edit/types"; import { useRouter } from "next/navigation"; import { Meeting } from "@/features/meetupDetail/types"; import { MeetupListResponse } from "@/features/meetup/types"; import { ReviewScore } from "@/types/common"; -import { headerQueryKeys } from "@/features/header/queries"; +import { meetupDetailQueryKeys } from "@/features/shared/queryKeys/meetupDetail"; +import { headerQueryKeys } from "@/features/shared/queryKeys/header"; +import { mypageQueryKeys } from "@/features/shared/queryKeys/mypage"; /** 모임 참여 뮤테이션 */ export function useJoinMutation(meetingId: number) { @@ -26,21 +27,28 @@ export function useJoinMutation(meetingId: number) { return useMutation({ mutationFn: () => postJoin(meetingId), onMutate: async () => { - await queryClient.cancelQueries({ queryKey: meetupDetailQueryKeys.meeting(meetingId) }); await queryClient.cancelQueries({ - queryKey: meetupDetailQueryKeys.participants(meetingId), + queryKey: meetupDetailQueryKeys.meeting.detail(meetingId), + }); + await queryClient.cancelQueries({ + queryKey: meetupDetailQueryKeys.participants.detail(meetingId), }); - const prevData = queryClient.getQueryData(meetupDetailQueryKeys.meeting(meetingId)); + const prevData = queryClient.getQueryData( + meetupDetailQueryKeys.meeting.detail(meetingId), + ); - queryClient.setQueryData(meetupDetailQueryKeys.meeting(meetingId), (oldData) => { - if (!oldData) return oldData; - return { - ...oldData, - isJoined: true, - participantCount: oldData.participantCount + 1, - }; - }); + queryClient.setQueryData( + meetupDetailQueryKeys.meeting.detail(meetingId), + (oldData) => { + if (!oldData) return oldData; + return { + ...oldData, + isJoined: true, + participantCount: oldData.participantCount + 1, + }; + }, + ); return { prevData }; }, @@ -49,20 +57,18 @@ export function useJoinMutation(meetingId: number) { }, onError: (error: Error, _variables, context) => { if (context?.prevData) { - queryClient.setQueryData(meetupDetailQueryKeys.meeting(meetingId), context.prevData); + queryClient.setQueryData(meetupDetailQueryKeys.meeting.detail(meetingId), context.prevData); } handleShowToast({ message: error.message, status: "error" }); }, onSettled: () => { - queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting(meetingId) }); + queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting.detail(meetingId) }); queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.participants(meetingId), + queryKey: meetupDetailQueryKeys.participants.detail(meetingId), }); queryClient.invalidateQueries({ queryKey: ["meetup", "list"] }); - queryClient.invalidateQueries({ queryKey: ["mypage", "meetups"] }); - queryClient.invalidateQueries({ queryKey: ["mypage", "created"] }); - queryClient.invalidateQueries({ queryKey: headerQueryKeys.notificationsCount }); - queryClient.invalidateQueries({ queryKey: headerQueryKeys.notifications }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups.all }); + queryClient.invalidateQueries({ queryKey: headerQueryKeys.notifications.all }); }, }); } @@ -78,23 +84,30 @@ export function useFavoriteMutation(meetingId: number) { // API 호출 전 낙관적 업데이트 onMutate: async () => { - await queryClient.cancelQueries({ queryKey: meetupDetailQueryKeys.meeting(meetingId) }); - await queryClient.cancelQueries({ queryKey: meetupDetailQueryKeys.related.all() }); + await queryClient.cancelQueries({ + queryKey: meetupDetailQueryKeys.meeting.detail(meetingId), + }); + await queryClient.cancelQueries({ queryKey: meetupDetailQueryKeys.related.all }); // 이전 데이터 백업 - const prevData = queryClient.getQueryData(meetupDetailQueryKeys.meeting(meetingId)); + const prevData = queryClient.getQueryData( + meetupDetailQueryKeys.meeting.detail(meetingId), + ); const prevRelatedQueries = queryClient.getQueriesData({ - queryKey: meetupDetailQueryKeys.related.all(), + queryKey: meetupDetailQueryKeys.related.all, }); // meetupDetail 캐시 낙관적 업데이트 - queryClient.setQueryData(meetupDetailQueryKeys.meeting(meetingId), (oldData) => { - if (!oldData) return oldData; - return { ...oldData, isFavorited: !oldData.isFavorited }; - }); + queryClient.setQueryData( + meetupDetailQueryKeys.meeting.detail(meetingId), + (oldData) => { + if (!oldData) return oldData; + return { ...oldData, isFavorited: !oldData.isFavorited }; + }, + ); queryClient.setQueriesData( - { queryKey: meetupDetailQueryKeys.related.all() }, + { queryKey: meetupDetailQueryKeys.related.all }, (oldData) => { if (!oldData || !oldData.data) return oldData; return { @@ -116,7 +129,7 @@ export function useFavoriteMutation(meetingId: number) { }, onError: (error: Error, _variables, context) => { if (context?.prevData) { - queryClient.setQueryData(meetupDetailQueryKeys.meeting(meetingId), context.prevData); + queryClient.setQueryData(meetupDetailQueryKeys.meeting.detail(meetingId), context.prevData); } if (context?.prevRelatedQueries) { context.prevRelatedQueries.forEach(([queryKey, oldData]) => { @@ -126,12 +139,11 @@ export function useFavoriteMutation(meetingId: number) { handleShowToast({ message: error.message, status: "error" }); }, onSettled: () => { - queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting(meetingId) }); + queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting.detail(meetingId) }); queryClient.invalidateQueries({ queryKey: headerQueryKeys.favorites }); queryClient.invalidateQueries({ queryKey: ["meetup", "list"] }); - queryClient.invalidateQueries({ queryKey: ["mypage", "meetups"] }); - queryClient.invalidateQueries({ queryKey: ["mypage", "created"] }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups.all }); }, }); } @@ -144,22 +156,29 @@ export function useCancelJoinMutation(meetingId: number) { return useMutation({ mutationFn: () => deleteJoin(meetingId), onMutate: async () => { - await queryClient.cancelQueries({ queryKey: meetupDetailQueryKeys.meeting(meetingId) }); await queryClient.cancelQueries({ - queryKey: meetupDetailQueryKeys.participants(meetingId), + queryKey: meetupDetailQueryKeys.meeting.detail(meetingId), + }); + await queryClient.cancelQueries({ + queryKey: meetupDetailQueryKeys.participants.detail(meetingId), }); - const prevData = queryClient.getQueryData(meetupDetailQueryKeys.meeting(meetingId)); + const prevData = queryClient.getQueryData( + meetupDetailQueryKeys.meeting.detail(meetingId), + ); // 낙관적 업데이트 — isJoined, participantCount 미리 변경 - queryClient.setQueryData(meetupDetailQueryKeys.meeting(meetingId), (oldData) => { - if (!oldData) return oldData; - return { - ...oldData, - isJoined: false, - participantCount: oldData.participantCount - 1, - }; - }); + queryClient.setQueryData( + meetupDetailQueryKeys.meeting.detail(meetingId), + (oldData) => { + if (!oldData) return oldData; + return { + ...oldData, + isJoined: false, + participantCount: oldData.participantCount - 1, + }; + }, + ); return { prevData }; }, @@ -170,21 +189,19 @@ export function useCancelJoinMutation(meetingId: number) { onError: (error: Error, _variables, context) => { if (context?.prevData) { - queryClient.setQueryData(meetupDetailQueryKeys.meeting(meetingId), context.prevData); + queryClient.setQueryData(meetupDetailQueryKeys.meeting.detail(meetingId), context.prevData); } handleShowToast({ message: error.message, status: "error" }); }, onSettled: () => { - queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting(meetingId) }); + queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting.detail(meetingId) }); queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.participants(meetingId), + queryKey: meetupDetailQueryKeys.participants.detail(meetingId), }); queryClient.invalidateQueries({ queryKey: ["meetup", "list"] }); - queryClient.invalidateQueries({ queryKey: ["mypage", "meetups"] }); - queryClient.invalidateQueries({ queryKey: ["mypage", "created"] }); - queryClient.invalidateQueries({ queryKey: headerQueryKeys.notifications }); - queryClient.invalidateQueries({ queryKey: headerQueryKeys.notificationsCount }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups.all }); + queryClient.invalidateQueries({ queryKey: headerQueryKeys.notifications.all }); }, }); } @@ -196,7 +213,7 @@ export function useEditMeetingMutation(meetingId: number) { return useMutation({ mutationFn: (data: MeetupEditData) => patchMeeting(meetingId, data), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting(meetingId) }); + queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting.detail(meetingId) }); }, }); } @@ -210,10 +227,9 @@ export function useDeleteMeetingMutation(meetingId: number) { return useMutation({ mutationFn: () => deleteMeeting(meetingId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting(meetingId) }); + queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.meeting.detail(meetingId) }); queryClient.invalidateQueries({ queryKey: ["meetup", "list"] }); - queryClient.invalidateQueries({ queryKey: ["mypage", "meetups"] }); - queryClient.invalidateQueries({ queryKey: ["mypage", "created"] }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups.all }); queryClient.invalidateQueries({ queryKey: headerQueryKeys.all }); handleShowToast({ message: "모임이 삭제되었습니다.", status: "success" }); router.replace("/meetup/list"); @@ -238,7 +254,7 @@ export function useEditReviewMutation(meetingId: number) { data: { score: ReviewScore; comment: string }; }) => patchReview(reviewId, data), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.reviews(meetingId) }); + queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.reviews.detail(meetingId) }); queryClient.invalidateQueries({ queryKey: ["reviews"] }); handleShowToast({ message: "리뷰가 수정되었습니다.", status: "success" }); }, @@ -256,7 +272,7 @@ export function useDeleteReviewMutation(meetingId: number) { return useMutation({ mutationFn: (reviewId: number) => deleteReview(reviewId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.reviews(meetingId) }); + queryClient.invalidateQueries({ queryKey: meetupDetailQueryKeys.reviews.detail(meetingId) }); queryClient.invalidateQueries({ queryKey: ["reviews"] }); handleShowToast({ message: "리뷰가 삭제되었습니다.", status: "success" }); }, diff --git a/features/meetupDetail/queries.ts b/features/meetupDetail/queries.ts index 105fcc5f..7895e9a2 100644 --- a/features/meetupDetail/queries.ts +++ b/features/meetupDetail/queries.ts @@ -7,22 +7,11 @@ import { } from "@/features/meetupDetail/apis/apis"; import { ParticipantsResponse } from "@/features/meetupDetail/types"; import { useMemo } from "react"; - -export const meetupDetailQueryKeys = { - meeting: (meetingId: number) => ["meetupDetail", "meeting", meetingId] as const, - participants: (meetingId: number) => ["meetupDetail", "participants", meetingId] as const, - reviews: (meetingId: number, cursor?: string) => - ["meetupDetail", "reviews", meetingId, cursor] as const, - related: { - all: () => ["meetupDetail", "related"] as const, - detail: (meetingId: number, region: string, type: string) => - ["meetupDetail", "related", meetingId, region, type] as const, - }, -}; +import { meetupDetailQueryKeys } from "@/features/shared/queryKeys/meetupDetail"; export function useMeetingDetail(meetingId: number) { return useSuspenseQuery({ - queryKey: meetupDetailQueryKeys.meeting(meetingId), + queryKey: meetupDetailQueryKeys.meeting.detail(meetingId), queryFn: () => getMeetingDetail(meetingId), staleTime: 1000 * 60 * 5, // 모임 정보: 5분 (자주 안바뀌므로) }); @@ -30,7 +19,7 @@ export function useMeetingDetail(meetingId: number) { export function useParticipants(meetingId: number) { const response = useSuspenseInfiniteQuery({ - queryKey: meetupDetailQueryKeys.participants(meetingId), + queryKey: meetupDetailQueryKeys.participants.detail(meetingId), queryFn: ({ pageParam }: { pageParam: string | undefined }) => getParticipants(meetingId, pageParam), initialPageParam: undefined as string | undefined, @@ -58,7 +47,7 @@ export function useParticipants(meetingId: number) { export function useReviews(meetingId: number, cursor?: string) { return useSuspenseQuery({ - queryKey: meetupDetailQueryKeys.reviews(meetingId, cursor), + queryKey: meetupDetailQueryKeys.reviews.detail(meetingId, cursor), queryFn: () => getReviews(meetingId, cursor), staleTime: 1000 * 60 * 10, // 리뷰: 10분 (가장 안바뀌므로) }); diff --git a/features/mypage/mutations.ts b/features/mypage/mutations.ts index e8e49851..4c8b2265 100644 --- a/features/mypage/mutations.ts +++ b/features/mypage/mutations.ts @@ -10,11 +10,12 @@ import { uploadProfileImage, } from "./apis"; import { useToast } from "@/providers/toast-provider"; -import { mypageQueryKeys } from "./queries"; -import { meetupDetailQueryKeys } from "../meetupDetail/queries"; -import { headerQueryKeys } from "../header/queries"; -import { meetupQueryKeys } from "../meetup/queries"; -import { reviewsQueryKeys } from "../reviews/queries/queryKeys"; +import { meetupDetailQueryKeys } from "@/features/shared/queryKeys/meetupDetail"; +import { authQueryKeys } from "@/features/shared/queryKeys/auth"; +import { mypageQueryKeys } from "@/features/shared/queryKeys/mypage"; +import { headerQueryKeys } from "@/features/shared/queryKeys/header"; +import { meetupQueryKeys } from "@/features/shared/queryKeys/meetup"; +import { reviewsQueryKeys } from "@/features/shared/queryKeys/reviews"; interface UsePatchUsersMeOptions { onSuccessBeforeSync?: () => void; @@ -54,7 +55,7 @@ export function usePatchUsersMe(options?: UsePatchUsersMeOptions) { message: "프로필이 수정되었습니다.", status: "success", }); - queryClient.invalidateQueries({ queryKey: ["me"] }); + queryClient.invalidateQueries({ queryKey: authQueryKeys.me }); }, onError: () => { @@ -82,9 +83,9 @@ export function usePatchMeetingsStatus() { status: "success", }); queryClient.invalidateQueries({ queryKey: headerQueryKeys.all }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetup.all }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups.all }); queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.meeting(variables.meetingId), + queryKey: meetupDetailQueryKeys.meeting.detail(variables.meetingId), }); queryClient.invalidateQueries({ queryKey: meetupQueryKeys.list }); }, @@ -113,9 +114,9 @@ export function useDeleteMeetings() { status: "success", }); queryClient.invalidateQueries({ queryKey: headerQueryKeys.all }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetup.all }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups.all }); queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.meeting(variables.meetingId), + queryKey: meetupDetailQueryKeys.meeting.detail(variables.meetingId), }); queryClient.invalidateQueries({ queryKey: meetupQueryKeys.list }); }, @@ -143,12 +144,12 @@ export function useDeleteMeetingsJoin() { status: "success", }); queryClient.invalidateQueries({ queryKey: headerQueryKeys.all }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetup.all }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups.all }); queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.meeting(variables.meetingId), + queryKey: meetupDetailQueryKeys.meeting.detail(variables.meetingId), }); queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.participants(variables.meetingId), + queryKey: meetupDetailQueryKeys.participants.detail(variables.meetingId), }); queryClient.invalidateQueries({ queryKey: meetupQueryKeys.list }); }, @@ -175,8 +176,7 @@ export function usePostMeetingsReviews() { message: `리뷰가 작성 되었습니다.`, status: "success", }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetup.all }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.review.all }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.all }); queryClient.invalidateQueries({ queryKey: reviewsQueryKeys.reviews.all }); }, @@ -202,8 +202,7 @@ export function usePatchReviews() { message: `리뷰가 수정 되었습니다.`, status: "success", }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetup.all }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.review.all }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.all }); queryClient.invalidateQueries({ queryKey: reviewsQueryKeys.reviews.all }); }, @@ -229,8 +228,7 @@ export function useDeleteReviews() { message: `리뷰가 삭제 되었습니다.`, status: "success", }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetup.all }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.review.all }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.all }); queryClient.invalidateQueries({ queryKey: reviewsQueryKeys.reviews.all }); }, diff --git a/features/mypage/queries.ts b/features/mypage/queries.ts index 8ae01316..a10af7c2 100644 --- a/features/mypage/queries.ts +++ b/features/mypage/queries.ts @@ -7,6 +7,7 @@ import { GetUsersMeMeetingsParams, getUsersMeReviews, } from "./apis"; +import { mypageQueryKeys } from "@/features/shared/queryKeys/mypage"; const DEFAULT_PARAMS = { sortBy: "dateTime", @@ -14,50 +15,12 @@ const DEFAULT_PARAMS = { size: 10, } as const; -const MYPAGE_QUERY_BASE_KEY = ["mypage"] as const; - -export const mypageQueryKeys = { - all: MYPAGE_QUERY_BASE_KEY, - - meetup: { - all: [...MYPAGE_QUERY_BASE_KEY, "meetups"] as const, - joined: [...MYPAGE_QUERY_BASE_KEY, "meetups", "joined"] as const, - joinedList: (params: GetUsersMeMeetingsParams = {}) => - [...MYPAGE_QUERY_BASE_KEY, "meetups", "joined", params] as const, - - created: [...MYPAGE_QUERY_BASE_KEY, "meetups", "created"] as const, - createdList: (params: GetUsersMeMeetingsParams = {}) => - [...MYPAGE_QUERY_BASE_KEY, "meetups", "created", params] as const, - }, - - review: { - all: [...MYPAGE_QUERY_BASE_KEY, "reviews"] as const, - available: [...MYPAGE_QUERY_BASE_KEY, "reviews", "available"] as const, - availableList: (params: GetMeetingsJoinedParams = {}) => - [...MYPAGE_QUERY_BASE_KEY, "reviews", "available", params] as const, - - written: [...MYPAGE_QUERY_BASE_KEY, "reviews", "written"] as const, - writtenList: (params: BaseListParams = {}) => - [...MYPAGE_QUERY_BASE_KEY, "reviews", "written", params] as const, - }, - - // @TODO 삭제예정 legacy - meetups: ["mypage", "meetups"] as const, - meetupsList: (params: GetMeetingsJoinedParams = {}) => ["mypage", "meetups", params] as const, - - reviews: ["mypage", "reviews"] as const, - reviewsList: (params: BaseListParams = {}) => ["mypage", "reviews", params] as const, - - created: ["mypage", "created"] as const, - createdList: (params: GetUsersMeMeetingsParams = {}) => ["mypage", "created", params] as const, -} as const; - // 내가 참여한 모임 목록 export function useMyJoinedInfinite(params: Omit = {}) { const mergedParams = { ...DEFAULT_PARAMS, ...params, type: "joined" as const }; return useSuspenseInfiniteQuery({ - queryKey: mypageQueryKeys.meetup.joinedList(mergedParams), + queryKey: mypageQueryKeys.meetups.joinedList(mergedParams), queryFn: ({ pageParam }) => getUsersMeMeetings({ ...mergedParams, @@ -80,7 +43,7 @@ export function useMyCreatedInfinite(params: Omit getUsersMeMeetings({ ...mergedParams, @@ -99,7 +62,7 @@ export function useMyMeetupInfinite(params: Omit getMeetingsJoined({ ...mergedParams, @@ -121,7 +84,7 @@ export function useMyReviewInfinite(params: Omit = {}) const mergedParams = { ...DEFAULT_PARAMS, sortBy: "createdAt" as const, ...params }; return useSuspenseInfiniteQuery({ - queryKey: mypageQueryKeys.review.writtenList(mergedParams), + queryKey: mypageQueryKeys.reviews.writtenList(mergedParams), queryFn: ({ pageParam }) => getUsersMeReviews({ ...mergedParams, diff --git a/features/reviews/components/RatingSummary/index.stories.tsx b/features/reviews/components/RatingSummary/index.stories.tsx index aa5ab6f0..d2890b15 100644 --- a/features/reviews/components/RatingSummary/index.stories.tsx +++ b/features/reviews/components/RatingSummary/index.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/nextjs-vite"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { reviewsQueryKeys } from "@/features/shared/queryKeys/reviews"; import RatingSummary from "."; -import { reviewsQueryKeys } from "../../queries/queryKeys"; const DEFAULT_STATISTICS = { averageScore: 4.5, diff --git a/features/reviews/mutations.ts b/features/reviews/mutations.ts index b407d67f..bcbf4895 100644 --- a/features/reviews/mutations.ts +++ b/features/reviews/mutations.ts @@ -1,8 +1,8 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useToast } from "@/providers/toast-provider"; -import { reviewsQueryKeys } from "../reviews/queries/queryKeys"; -import { mypageQueryKeys } from "../mypage/queries"; import { deleteReviews, patchReviews } from "./apis/client"; +import { mypageQueryKeys } from "@/features/shared/queryKeys/mypage"; +import { reviewsQueryKeys } from "@/features/shared/queryKeys/reviews"; export function usePatchReviews() { const queryClient = useQueryClient(); @@ -16,8 +16,7 @@ export function usePatchReviews() { message: `리뷰가 수정 되었습니다.`, status: "success", }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.reviews }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.reviews.all }); queryClient.invalidateQueries({ queryKey: reviewsQueryKeys.reviews.all }); }, @@ -42,8 +41,7 @@ export function useDeleteReviews() { message: `리뷰가 삭제 되었습니다.`, status: "success", }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.meetups }); - queryClient.invalidateQueries({ queryKey: mypageQueryKeys.reviews }); + queryClient.invalidateQueries({ queryKey: mypageQueryKeys.all }); queryClient.invalidateQueries({ queryKey: reviewsQueryKeys.reviews.all }); }, diff --git a/features/reviews/queries/queryOptions.ts b/features/reviews/queries/queryOptions.ts index 301c6cf2..a5eebf77 100644 --- a/features/reviews/queries/queryOptions.ts +++ b/features/reviews/queries/queryOptions.ts @@ -5,7 +5,7 @@ import { ReviewsListRequest, ReviewsListResponse, } from "../types"; -import { reviewsQueryKeys } from "./queryKeys"; +import { reviewsQueryKeys } from "@/features/shared/queryKeys/reviews"; export function reviewsInfiniteOptions( params: ReviewsListRequest, diff --git a/features/shared/queryKeys/auth.ts b/features/shared/queryKeys/auth.ts new file mode 100644 index 00000000..96324cfc --- /dev/null +++ b/features/shared/queryKeys/auth.ts @@ -0,0 +1,5 @@ +const AUTH_QUERY_BASE_KEY = ["me"] as const; + +export const authQueryKeys = { + me: AUTH_QUERY_BASE_KEY, +} as const; diff --git a/features/shared/queryKeys/connect.ts b/features/shared/queryKeys/connect.ts new file mode 100644 index 00000000..d9fd72f8 --- /dev/null +++ b/features/shared/queryKeys/connect.ts @@ -0,0 +1,22 @@ +const CONNECT_QUERY_BASE_KEY = ["connect"] as const; + +export const connectQueryKeys = { + // 게시글 관련 전체 키 (invalidateQueries 범위 제어용) + all: CONNECT_QUERY_BASE_KEY, + + // 게시글 목록 관련 키 + lists: [...CONNECT_QUERY_BASE_KEY, "list"] as const, + + // 게시글 목록 필터 조합 키 + list: (page: number, sortBy: string, limit: number, keyword: string) => + [...CONNECT_QUERY_BASE_KEY, "list", { page, sortBy, limit, keyword }] as const, + + // 인기 게시글 키 + hotPosts: [...CONNECT_QUERY_BASE_KEY, "hotPosts"] as const, + + // 게시글 상세 키 + detail: (postId: number) => [...CONNECT_QUERY_BASE_KEY, "detail", postId] as const, + + // 유저 프로필 키 + userProfile: (userId: number) => ["user", "profile", userId] as const, +} as const; diff --git a/features/favorites/queries/queryKeys.ts b/features/shared/queryKeys/favorites.ts similarity index 55% rename from features/favorites/queries/queryKeys.ts rename to features/shared/queryKeys/favorites.ts index aa07481f..cecc4e79 100644 --- a/features/favorites/queries/queryKeys.ts +++ b/features/shared/queryKeys/favorites.ts @@ -1,8 +1,9 @@ -import { FavoriteMeetingsListKeyParams } from "../types"; +import { FavoriteMeetingsListKeyParams } from "@/features/favorites/types"; const FAVORITES_QUERY_BASE_KEY = ["favorites"] as const; -export const queryKeys = { +/** 찜하기/찜해제 관련된 경우 = favoritesQueryKey.all */ +export const favoritesQueryKeys = { favorites: { all: FAVORITES_QUERY_BASE_KEY, list: (params: FavoriteMeetingsListKeyParams) => diff --git a/features/shared/queryKeys/header.ts b/features/shared/queryKeys/header.ts new file mode 100644 index 00000000..fc306940 --- /dev/null +++ b/features/shared/queryKeys/header.ts @@ -0,0 +1,15 @@ +/** + * 찜 등록/해제 = favorites + * 모임 삭제 (찜 갯수와 알림 모두 갱신) = all + * 모임 참여, 참여취소,확정,취소,게시글 댓글 = notifications.all + */ +const HEADER_QUERY_BASE_KEY = ["header"] as const; +export const headerQueryKeys = { + // 찜 개수 + all: HEADER_QUERY_BASE_KEY, + favorites: [...HEADER_QUERY_BASE_KEY, "favorites"] as const, + notifications: { + all: [...HEADER_QUERY_BASE_KEY, "notifications"] as const, + count: [...HEADER_QUERY_BASE_KEY, "notifications", "count"] as const, + }, +} as const; diff --git a/features/shared/queryKeys/meetup.ts b/features/shared/queryKeys/meetup.ts new file mode 100644 index 00000000..e81f51e7 --- /dev/null +++ b/features/shared/queryKeys/meetup.ts @@ -0,0 +1,19 @@ +import { MeetupListRequest } from "@/features/meetup/types"; + +const MEETUP_QUERY_BASE_KEY = ["meetup"] as const; + +export const meetupQueryKeys = { + all: MEETUP_QUERY_BASE_KEY, + list: [...MEETUP_QUERY_BASE_KEY, "list"] as const, + listWithParams: (params: MeetupListRequest) => + [...MEETUP_QUERY_BASE_KEY, "list", params] as const, +} as const; + +export const meetupMutationKeys = { + postMeetup: ["meetup", "post"] as const, + uploadImage: ["meetup", "image", "upload"] as const, + postFavorite: ["meetings", "favorite", "post"] as const, + deleteFavorite: ["meetings", "favorite", "delete"] as const, + postJoin: ["meetings", "join", "post"] as const, + deleteJoin: ["meetings", "join", "delete"] as const, +}; diff --git a/features/shared/queryKeys/meetupDetail.ts b/features/shared/queryKeys/meetupDetail.ts new file mode 100644 index 00000000..0729f71a --- /dev/null +++ b/features/shared/queryKeys/meetupDetail.ts @@ -0,0 +1,28 @@ +const MEETUP_DETAIL_QUERY_BASE_KEY = ["meetupDetail"] as const; + +export const meetupDetailQueryKeys = { + all: MEETUP_DETAIL_QUERY_BASE_KEY, + + meeting: { + all: [...MEETUP_DETAIL_QUERY_BASE_KEY, "meeting"] as const, + detail: (meetingId: number) => [...MEETUP_DETAIL_QUERY_BASE_KEY, "meeting", meetingId] as const, + }, + + participants: { + all: [...MEETUP_DETAIL_QUERY_BASE_KEY, "participants"] as const, + detail: (meetingId: number) => + [...MEETUP_DETAIL_QUERY_BASE_KEY, "participants", meetingId] as const, + }, + + reviews: { + all: [...MEETUP_DETAIL_QUERY_BASE_KEY, "reviews"] as const, + detail: (meetingId: number, cursor?: string) => + [...MEETUP_DETAIL_QUERY_BASE_KEY, "reviews", meetingId, cursor] as const, + }, + + related: { + all: [...MEETUP_DETAIL_QUERY_BASE_KEY, "related"] as const, + detail: (meetingId: number, region: string, type: string) => + [...MEETUP_DETAIL_QUERY_BASE_KEY, "related", meetingId, region, type] as const, + }, +} as const; diff --git a/features/mypage/queryKey.ts b/features/shared/queryKeys/mypage.ts similarity index 73% rename from features/mypage/queryKey.ts rename to features/shared/queryKeys/mypage.ts index 397e919a..3a50f11f 100644 --- a/features/mypage/queryKey.ts +++ b/features/shared/queryKeys/mypage.ts @@ -1,4 +1,8 @@ -import { BaseListParams, GetMeetingsJoinedParams, GetUsersMeMeetingsParams } from "./apis"; +import { + BaseListParams, + GetMeetingsJoinedParams, + GetUsersMeMeetingsParams, +} from "@/features/mypage/apis"; /** * @@ -40,19 +44,3 @@ export const mypageQueryKeys = { [...MYPAGE_QUERY_BASE_KEY, "reviews", "written", params] as const, }, } as const; - -/** - * 찜 등록/해제 = favorites - * 모임 삭제 (찜 갯수와 알림 모두 갱신) = all - * 모임 참여, 참여취소,확정,취소,게시글 댓글 = notifications.all - */ -const HEADER_QUERY_BASE_KEY = ["header"] as const; -export const headerQueryKeys = { - // 찜 개수 - all: HEADER_QUERY_BASE_KEY, - favorites: [...HEADER_QUERY_BASE_KEY, "favorites"] as const, - notifications: { - all: [...HEADER_QUERY_BASE_KEY, "notifications"] as const, - count: [...HEADER_QUERY_BASE_KEY, "notifications", "count"] as const, - }, -} as const; diff --git a/features/reviews/queries/queryKeys.ts b/features/shared/queryKeys/reviews.ts similarity index 60% rename from features/reviews/queries/queryKeys.ts rename to features/shared/queryKeys/reviews.ts index 31549ceb..df86c46f 100644 --- a/features/reviews/queries/queryKeys.ts +++ b/features/shared/queryKeys/reviews.ts @@ -1,12 +1,16 @@ -import { ReviewsListKeyParams } from "../types"; +import { ReviewsListKeyParams } from "@/features/reviews/types"; const REVIEWS_QUERY_BASE_KEY = ["reviews"] as const; +/** 리뷰 관련한경우 = reviewsQueryKeys.reviews.all */ export const reviewsQueryKeys = { reviews: { all: REVIEWS_QUERY_BASE_KEY, + /** 모든 리뷰 리스트 */ list: (params: ReviewsListKeyParams) => [...REVIEWS_QUERY_BASE_KEY, "list", params] as const, + /** 모든 리뷰 총점 평균 통계 */ statistics: [...REVIEWS_QUERY_BASE_KEY, "statistics"] as const, + /** 모든 리뷰 타입 별 총점 평균 통계 */ categories: { statistics: [...REVIEWS_QUERY_BASE_KEY, "categories", "statistics"] as const, }, diff --git a/hooks/useMeetingFavorite.ts b/hooks/useMeetingFavorite.ts index 60cc3b7e..73efb1f2 100644 --- a/hooks/useMeetingFavorite.ts +++ b/hooks/useMeetingFavorite.ts @@ -2,11 +2,11 @@ import { CursorPageResponse, MeetupList } from "@/features/mypage/types"; import { InfiniteData, useMutation, useQueryClient } from "@tanstack/react-query"; -import { headerQueryKeys } from "@/features/header/queries"; import { deleteMeetingsFavorites, postMeetingsFavorites } from "@/features/mypage/apis"; -import { meetupDetailQueryKeys } from "@/features/meetupDetail/queries"; -import { meetupQueryKeys } from "@/features/meetup/queries"; -import { mypageQueryKeys } from "@/features/mypage/queries"; +import { meetupDetailQueryKeys } from "@/features/shared/queryKeys/meetupDetail"; +import { mypageQueryKeys } from "@/features/shared/queryKeys/mypage"; +import { headerQueryKeys } from "@/features/shared/queryKeys/header"; +import { meetupQueryKeys } from "@/features/shared/queryKeys/meetup"; /** * 찜 추가 시 낙관적 업데이트 및 롤백 하는 훅 @@ -17,8 +17,8 @@ import { mypageQueryKeys } from "@/features/mypage/queries"; */ const favoriteQueryPrefixes = [ - mypageQueryKeys.meetup.all, - mypageQueryKeys.review.available, + mypageQueryKeys.meetups.all, + mypageQueryKeys.reviews.available, ] as const; export default function useMeetingFavorite() { @@ -67,7 +67,7 @@ export default function useMeetingFavorite() { queryClient.invalidateQueries({ queryKey: headerQueryKeys.favorites }); queryClient.invalidateQueries({ queryKey: meetupQueryKeys.list }); queryClient.invalidateQueries({ - queryKey: meetupDetailQueryKeys.meeting(variables.meetingId), + queryKey: meetupDetailQueryKeys.meeting.detail(variables.meetingId), }); queryClient.invalidateQueries({ queryKey: ["favorites"] }); },