diff --git a/apps/web/src/apis/community/deletePost.ts b/apps/web/src/apis/community/deletePost.ts index a3307b17..ab965a33 100644 --- a/apps/web/src/apis/community/deletePost.ts +++ b/apps/web/src/apis/community/deletePost.ts @@ -3,27 +3,64 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import type { AxiosError, AxiosResponse } from "axios"; import { useRouter } from "next/navigation"; +import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; import { CommunityQueryKeys, communityApi, type DeletePostResponse } from "./api"; +interface DeletePostVariables { + postId: number; + boardCode?: string; +} + +/** + * @description ISR 페이지를 revalidate하는 함수 + * @param boardCode - 게시판 코드 + * @param accessToken - 사용자 인증 토큰 + */ +const revalidateCommunityPage = async (boardCode: string, accessToken: string) => { + try { + if (!accessToken) { + console.warn("Revalidation skipped: No access token available"); + return; + } + + await fetch("/api/revalidate", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ boardCode }), + }); + } catch (error) { + console.error("Revalidate failed:", error); + } +}; + /** * @description 게시글 삭제를 위한 useMutation 커스텀 훅 */ const useDeletePost = () => { const router = useRouter(); const queryClient = useQueryClient(); + const { accessToken } = useAuthStore(); - return useMutation, AxiosError, number>({ - mutationFn: communityApi.deletePost, - onSuccess: () => { + return useMutation, AxiosError, DeletePostVariables>({ + mutationFn: ({ postId }) => communityApi.deletePost(postId), + onSuccess: async (_result, variables) => { // 'posts' 쿼리 키를 가진 모든 쿼리를 무효화하여 // 게시글 목록을 다시 불러오도록 합니다. queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] }); + // ISR 페이지 revalidate + if (variables.boardCode && accessToken) { + await revalidateCommunityPage(variables.boardCode, accessToken); + } + toast.success("게시글이 성공적으로 삭제되었습니다."); // 게시글 목록 페이지 이동 - router.replace("/community/FREE"); + router.replace(`/community/${variables.boardCode || "FREE"}`); }, onError: (error) => { console.error("게시글 삭제 실패:", error); diff --git a/apps/web/src/apis/community/patchUpdatePost.ts b/apps/web/src/apis/community/patchUpdatePost.ts index ba2be8a0..bec18c47 100644 --- a/apps/web/src/apis/community/patchUpdatePost.ts +++ b/apps/web/src/apis/community/patchUpdatePost.ts @@ -1,26 +1,60 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import type { AxiosError } from "axios"; +import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; import { CommunityQueryKeys, communityApi, type PostIdResponse, type PostUpdateRequest } from "./api"; interface UpdatePostVariables { postId: number; data: PostUpdateRequest; + boardCode?: string; } +/** + * @description ISR 페이지를 revalidate하는 함수 + * @param boardCode - 게시판 코드 + * @param accessToken - 사용자 인증 토큰 + */ +const revalidateCommunityPage = async (boardCode: string, accessToken: string) => { + try { + if (!accessToken) { + console.warn("Revalidation skipped: No access token available"); + return; + } + + await fetch("/api/revalidate", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ boardCode }), + }); + } catch (error) { + console.error("Revalidate failed:", error); + } +}; + /** * @description 게시글 수정을 위한 useMutation 커스텀 훅 */ const useUpdatePost = () => { const queryClient = useQueryClient(); + const { accessToken } = useAuthStore(); return useMutation({ mutationFn: ({ postId, data }) => communityApi.updatePost(postId, data), - onSuccess: (_result, variables) => { + onSuccess: async (_result, variables) => { // 해당 게시글 상세 쿼리와 목록 쿼리를 무효화 queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts, variables.postId] }); queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] }); + + // ISR 페이지 revalidate + if (variables.boardCode && accessToken) { + await revalidateCommunityPage(variables.boardCode, accessToken); + } + toast.success("게시글이 수정되었습니다."); }, onError: (error) => { diff --git a/apps/web/src/app/community/[boardCode]/[postId]/KebabMenu.tsx b/apps/web/src/app/community/[boardCode]/[postId]/KebabMenu.tsx index a67e0af9..8146ede3 100644 --- a/apps/web/src/app/community/[boardCode]/[postId]/KebabMenu.tsx +++ b/apps/web/src/app/community/[boardCode]/[postId]/KebabMenu.tsx @@ -1,16 +1,19 @@ "use client"; import { useRouter } from "next/navigation"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, type RefObject } from "react"; import { useDeletePost } from "@/apis/community"; import ReportPanel from "@/components/ui/ReportPanel"; import { toast } from "@/lib/zustand/useToastStore"; import { IconSetting } from "@/public/svgs/mentor"; -const useClickOutside = (ref, handler) => { +const useClickOutside = (ref: RefObject, handler: (event: MouseEvent | TouchEvent) => void) => { useEffect(() => { - const listener = (event) => { - if (!ref.current || ref.current.contains(event.target)) return; + const listener = (event: MouseEvent | TouchEvent) => { + const current = ref.current; + if (!current) return; + if (!(event.target instanceof Node)) return; + if (current.contains(event.target)) return; handler(event); }; document.addEventListener("mousedown", listener); @@ -113,7 +116,7 @@ const KebabMenu = ({ postId, boardCode, isOwner = false }: KebabMenuProps) => { 질문으로 업로드 하기 diff --git a/apps/web/src/app/mentor/modify/_ui/ModifyContent/_hooks/useModifyHookForm.ts b/apps/web/src/app/mentor/modify/_ui/ModifyContent/_hooks/useModifyHookForm.ts index 86bf3fda..451c91c0 100644 --- a/apps/web/src/app/mentor/modify/_ui/ModifyContent/_hooks/useModifyHookForm.ts +++ b/apps/web/src/app/mentor/modify/_ui/ModifyContent/_hooks/useModifyHookForm.ts @@ -24,7 +24,7 @@ const useModifyHookForm = (myMentorProfile: MentorCardPreview | null): UseModify reset({ channels, introduction: myMentorProfile.introduction, - passTip: "", + passTip: myMentorProfile.passTip ?? "", }); } else { // myMentorProfile이 없을 때도 4개의 빈 채널 슬롯 제공 @@ -35,6 +35,6 @@ const useModifyHookForm = (myMentorProfile: MentorCardPreview | null): UseModify }); } }, [myMentorProfile, reset]); - return { ...method }; + return method; }; export default useModifyHookForm; diff --git a/apps/web/src/app/mentor/modify/_ui/ModifyContent/index.tsx b/apps/web/src/app/mentor/modify/_ui/ModifyContent/index.tsx index 2fa6d9a4..ed28086a 100644 --- a/apps/web/src/app/mentor/modify/_ui/ModifyContent/index.tsx +++ b/apps/web/src/app/mentor/modify/_ui/ModifyContent/index.tsx @@ -61,8 +61,9 @@ const ModifyContent = () => {

멘토 한마디