diff --git a/client/src/app/(protectedRoute)/edit-post/[pid]/layout.tsx b/client/src/app/(protectedRoute)/edit-post/[pid]/layout.tsx new file mode 100644 index 0000000..ca6c5a7 --- /dev/null +++ b/client/src/app/(protectedRoute)/edit-post/[pid]/layout.tsx @@ -0,0 +1,24 @@ +import CustomAppbar from "@/components/layout/CustomAppbar"; +import { ReactNode, Suspense } from "react"; +import CustomContainer from "@/components/layout/CustomContainer"; + +type Props = { + children: ReactNode; +}; + +const layout = ({ children }: Props) => { + return ( + + + + + } + > + {children} + + ); +}; + +export default layout; diff --git a/client/src/app/(protectedRoute)/edit-post/[pid]/page.tsx b/client/src/app/(protectedRoute)/edit-post/[pid]/page.tsx new file mode 100644 index 0000000..29845a7 --- /dev/null +++ b/client/src/app/(protectedRoute)/edit-post/[pid]/page.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import HOME from "@/const/clientPath"; +import { useGlobalLoadingStore } from "@/store/useGlobalLoadingStore"; +import { useInvalidatePostList } from "@/queries/post/useGetPostListInfiniteQuery"; +import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface"; + +import CustomAppbar from "@/components/layout/CustomAppbar"; +import CustomContainer from "@/components/layout/CustomContainer"; +import useSubmitEditPostMutation from "@/queries/newPost/useSubmitEditPostMutation"; +import PostEditor from "@/components/newpost/PostEditor"; +import useGetPostDetailQuery, { useInvalidatePostDetail } from "@/queries/post/useGetPostDetailQuery"; + +export default function EditPostPage({ params }: { params: { pid: string } }) { + const { setLoading } = useGlobalLoadingStore(); + + const router = useRouter(); + const invalidatePreviousPost = useInvalidatePostList(); + const invalidatePostDetail = useInvalidatePostDetail() + + const { data: initialData } = useGetPostDetailQuery(params.pid); + const { alcoholName, alcoholNo, alcoholType, postContent, postAttachUrls,postNo } = + initialData; + + const [formData, setFormData] = useState(); + const [file, setFile] = useState(); + + const { mutateAsync: submitHandler, isSuccess } = useSubmitEditPostMutation({ + onMutate: () => setLoading(true), + onSuccess: () => { + invalidatePreviousPost(); + invalidatePostDetail(String(postNo)) + router.push(HOME); + }, + onSettled: () => setLoading(false), + }); + + return ( + <> + {/* 최상단 앱바 */} + + formData && + submitHandler({ + postNo: String(initialData.postNo), + formData, + file, + prevFileNo: initialData.postAttachUrls?.[0]?.attachNo, + }) + } + /> + + setFormData(formData)} + onFileChange={(file) => setFile(file)} + initialAlcohol={ + alcoholName ? { alcoholName, alcoholNo, alcoholType } : undefined + } + initialContent={postContent} + initialImage={(postAttachUrls ?? [])[0]?.attachUrl} + /> + + + ); +} diff --git a/client/src/app/(protectedRoute)/new-post/page.tsx b/client/src/app/(protectedRoute)/new-post/page.tsx index 182667b..373b8f4 100644 --- a/client/src/app/(protectedRoute)/new-post/page.tsx +++ b/client/src/app/(protectedRoute)/new-post/page.tsx @@ -1,128 +1,47 @@ "use client"; -import { Box, Container, Paper, Tooltip } from "@mui/material"; - import { useRouter } from "next/navigation"; -import { useCallback, useState } from "react"; +import { useState } from "react"; import HOME from "@/const/clientPath"; -import PictureIcon from "@/assets/icons/PictureIcon.svg"; -import PinIcon from "@/assets/icons/PinIcon.svg"; import { useGlobalLoadingStore } from "@/store/useGlobalLoadingStore"; -import useNewPostMutation from "@/queries/newPost/useNewPostMutation"; -import useNewAttachMutation from "@/queries/attach/useNewAttachMutation"; import { useInvalidatePostList } from "@/queries/post/useGetPostListInfiniteQuery"; -import { useDeletePostMutation } from "@/queries/post/useDeletePostMutation"; -import { - NewPostRequestInterface, - NewPostRequestAlCohol, -} from "@/types/newPost/NewPostInterface"; -import SearchAlcoholInput from "@/components/newpost/SearchAlcoholInput"; +import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface"; + import CustomAppbar from "@/components/layout/CustomAppbar"; -import SquareIconButton from "@/components/SquareIconButton"; -import PreviewImageByURL from "@/components/PreviewImageByURL"; -import NewPostTextEditor from "@/components/newpost/NewPostTextEditor"; -import useRenderAsDataUrl from "@/hooks/useRenderAsDataUrl"; -import SingleImageInput from "@/components/SingleImageInput"; -import { POST_IMAGE_SIZE } from "@/const/imageSize"; import CustomContainer from "@/components/layout/CustomContainer"; +import useSubmitPostMutation from "@/queries/newPost/useSubmitPostMutation"; +import PostEditor from "@/components/newpost/PostEditor"; export default function NewpostPage() { const { setLoading } = useGlobalLoadingStore(); + const router = useRouter(); const invalidatePreviousPost = useInvalidatePostList(); - const [formValue, setFormValue] = useState({ - postContent: "", - postType: "BASIC", - positionInfo: "", - tagList: [] as string[], - }); - - const [alcoholNo, setAlcoholNo] = - useState(); - + const [formValue, setFormValue] = useState(); const [file, setFile] = useState(); - const fileUrl = useRenderAsDataUrl(file); - const [isSuccess, SetIsSuccess] = useState(false); - - const { mutateAsync: newPostHandler } = useNewPostMutation(); - const { mutateAsync: attachFileHandler } = useNewAttachMutation(); - const { mutateAsync: deletePostHandler } = useDeletePostMutation(); - - const submitHandler = useCallback( - async (formValue: NewPostRequestInterface, file?: File) => { - setLoading(true); - let postNo; - try { - const { postNo: res } = await newPostHandler(formValue); - postNo = res; - if (file) { - try { - await attachFileHandler({ - file, - url: { pk: postNo, type: "POST" }, - size: POST_IMAGE_SIZE, - }); - } catch { - deletePostHandler(postNo); - return; - } - } - invalidatePreviousPost(); - SetIsSuccess(true); - router.push(HOME); - } catch { - return; - } finally { - setLoading(false); - } + const { mutateAsync: submitHandler, isSuccess } = useSubmitPostMutation({ + onMutate: () => setLoading(true), + onSuccess: () => { + invalidatePreviousPost(); + router.push(HOME); }, - [router] - ); + onSettled: () => setLoading(false), + }); return ( - + <> {/* 최상단 앱바 */} submitHandler({ ...formValue, alcoholNo }, file)} + onClickAppend={() => formValue && submitHandler({ formValue, file })} /> - - {/* 검색창 */} - - {/* 내용 */} - - setFormValue((prev) => ({ - ...prev, - postContent: content, - tagList, - })) - } - /> - {/* 파일 미리보기 */} - {fileUrl && } - {/* 버튼 그룹 */} - - {/* 사진 */} - - } - > - setFile(file)} /> - - - {/* 위치 */} - - } /> - - + - + ); } diff --git a/client/src/components/PreviewImageByURL.tsx b/client/src/components/PreviewImageByURL.tsx index 9a561d3..1d903b8 100644 --- a/client/src/components/PreviewImageByURL.tsx +++ b/client/src/components/PreviewImageByURL.tsx @@ -14,13 +14,13 @@ const PreviewImageByURL = ( sx={{ backgroundImage: `url(${fileUrl})`, width: "100%", - height: 142, borderRadius: 4, border: "1px solid", borderColor: "gray.secondary", backgroundRepeat: "no-repeat", backgroundSize: "cover", backgroundPosition: "center", + aspectRatio: 2.36, ...sx, }} ref={ref} diff --git a/client/src/components/newpost/NewPostTextEditor.tsx b/client/src/components/newpost/NewPostTextEditor.tsx index 2058f1b..fe48693 100644 --- a/client/src/components/newpost/NewPostTextEditor.tsx +++ b/client/src/components/newpost/NewPostTextEditor.tsx @@ -9,15 +9,20 @@ import { sanitize } from "isomorphic-dompurify"; interface NewPostTextEditorInterface { onContentChange: (props: { content: string; tagList: string[] }) => void; - maxLength?:number + initialValue?: string; + maxLength?: number; } -const NewPostTextEditor = ({ onContentChange,maxLength=200 }: NewPostTextEditorInterface) => { +const NewPostTextEditor = ({ + onContentChange, + maxLength = 200, + initialValue, +}: NewPostTextEditorInterface) => { const [_mentioningValue, setMentioningValue] = useState(""); const [tagList, setTagList] = useState([]); - const [content, setContent] = useState(""); + const [content, setContent] = useState(initialValue ?? ""); const [textLength, setTextLength] = useState(0); useEffect(() => { @@ -60,6 +65,7 @@ const NewPostTextEditor = ({ onContentChange,maxLength=200 }: NewPostTextEditorI modules={modules} placeholder="입력해주세요" onChange={(content, _d, _s, editor) => { + const textLength = editor.getLength() - 1; const parsedTags = editor .getContents() .filter((op) => op.insert?.mention?.value) @@ -68,8 +74,18 @@ const NewPostTextEditor = ({ onContentChange,maxLength=200 }: NewPostTextEditorI [] ); setTagList(parsedTags); - setContent(content); - setTextLength(editor.getLength() - 1); + setTextLength((prev) => { + if (textLength < maxLength) { + return textLength; + } + return prev; + }); + setContent((prev) => { + if (textLength < maxLength) { + return content; + } + return prev; + }); }} value={content} /> diff --git a/client/src/components/newpost/PostEditor.tsx b/client/src/components/newpost/PostEditor.tsx new file mode 100644 index 0000000..7de2236 --- /dev/null +++ b/client/src/components/newpost/PostEditor.tsx @@ -0,0 +1,100 @@ +import { Box, Tooltip } from "@mui/material"; +import SquareIconButton from "@/components/SquareIconButton"; +import PreviewImageByURL from "@/components/PreviewImageByURL"; +import NewPostTextEditor from "@/components/newpost/NewPostTextEditor"; +import SearchAlcoholInput from "@/components/newpost/SearchAlcoholInput"; +import PictureIcon from "@/assets/icons/PictureIcon.svg"; +import PinIcon from "@/assets/icons/PinIcon.svg"; +import SingleImageInput from "@/components/SingleImageInput"; +import { useEffect, useState } from "react"; +import useRenderAsDataUrl from "@/hooks/useRenderAsDataUrl"; +import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface"; +import { AlcoholDetailInterface } from "@/types/alcohol/AlcoholInterface"; + +interface PostEditorProps { + initialContent?: string; + initialAlcohol?: Pick< + AlcoholDetailInterface, + "alcoholName" | "alcoholNo" | "alcoholType" + >; + initialImage?: string; + /** + * formvalue와 file 을 인자로 넘겨주는 커스텀이벤트핸들러 + * @param props {formvalue,file} + * @returns + */ + onFormChange: (formValue: NewPostRequestInterface) => void; + onFileChange: (file?: File) => void; +} + +const PostEditor = ({ + initialContent, + initialAlcohol, + initialImage, + onFormChange, + onFileChange, +}: PostEditorProps) => { + const [formValue, setFormValue] = useState({ + postType: "BASIC", + positionInfo: "", + tagList: [] as string[], + }); + + useEffect(() => { + onFormChange(formValue); + }, [formValue]); + + const [file, setFile] = useState(); + + useEffect(() => { + onFileChange(file); + }, [file]); + + const fileUrl = useRenderAsDataUrl(file) ?? initialImage; + + return ( + <> + {/* 검색창 */} + + setFormValue((prev) => ({ ...prev, alcoholNo })) + } + /> + {/* 내용 */} + + setFormValue((prev) => ({ + ...prev, + postContent: content, + tagList, + })) + } + initialValue={initialContent} + /> + {/* 파일 미리보기 */} + {(fileUrl) && ( + + )} + {/* 버튼 그룹 */} + + {/* 사진 */} + + }> + { + setFile(file); + }} + /> + + + {/* 위치 */} + + } /> + + + + ); +}; + +export default PostEditor; diff --git a/client/src/components/newpost/SearchAlcoholInput.tsx b/client/src/components/newpost/SearchAlcoholInput.tsx index 1d2ceb1..f2dc595 100644 --- a/client/src/components/newpost/SearchAlcoholInput.tsx +++ b/client/src/components/newpost/SearchAlcoholInput.tsx @@ -10,7 +10,7 @@ import { Typography, InputAdornment, } from "@mui/material"; -import { Dispatch, SetStateAction, memo, useEffect, useState } from "react"; +import { memo, useEffect, useState } from "react"; import AlcholeSearchIcon from "@/assets/icons/AlcholeSearchIcon.svg"; import InputSearchIcon from "@/assets/icons/InputSearchIcon.svg"; import useGetAlcoholListQuery from "@/queries/alcohol/useGetAlcoholListQuery"; @@ -20,11 +20,18 @@ import useDebounce from "@/hooks/useDebounce"; import { NewPostRequestAlCohol } from "@/types/newPost/NewPostInterface"; interface SearchAlcoholInputInterface { - setAlcoholNo: Dispatch>; + setAlcoholNo: (alcoholNo: NewPostRequestAlCohol["alcoholNo"]) => void; + initialValue?: Pick< + AlcoholDetailInterface, + "alcoholName" | "alcoholNo" | "alcoholType" + >; } -const SearchAlcoholInput = ({ setAlcoholNo }: SearchAlcoholInputInterface) => { +const SearchAlcoholInput = ({ + setAlcoholNo, + initialValue, +}: SearchAlcoholInputInterface) => { // 유저가 검색한 키워드 - const [searchKeyword, setSearchKeyword] = useState(); + const [searchKeyword, setSearchKeyword] = useState(""); // 검색한 키워드의 Debounced 값 const debouncedValue = useDebounce(searchKeyword, 300); const [isSearchingAlcohol, setIsSearchingAlCohol] = useState(false); @@ -32,11 +39,13 @@ const SearchAlcoholInput = ({ setAlcoholNo }: SearchAlcoholInputInterface) => { // 검색결과 const { data, isLoading, isSuccess } = useGetAlcoholListQuery(debouncedValue); // 유저가 검색후 최종적으로 선택한 값 - const [selectedAlcohol, setSelectedAlcohol] = - useState(); + const [selectedAlcohol, setSelectedAlcohol] = useState< + | Pick + | undefined + >(initialValue); useEffect(() => { - setSearchKeyword(selectedAlcohol?.alcoholName); + setSearchKeyword(selectedAlcohol?.alcoholName ?? ""); setAlcoholNo(selectedAlcohol?.alcoholNo); }, [selectedAlcohol]); diff --git a/client/src/components/post/PostCardOptionDropdown.tsx b/client/src/components/post/PostCardOptionDropdown.tsx index da4155e..4988fc6 100644 --- a/client/src/components/post/PostCardOptionDropdown.tsx +++ b/client/src/components/post/PostCardOptionDropdown.tsx @@ -1,10 +1,7 @@ -import React, { useState } from "react"; -import { MoreVertOutlined } from "@mui/icons-material"; -import { ButtonBase, Menu, MenuItem } from "@mui/material"; import { useDeletePostMutation } from "@/queries/post/useDeletePostMutation"; import useDeleteAttachMutation from "@/queries/attach/useDeleteAttachMutation"; import { useRouter } from "next/navigation"; -import HOME from "@/const/clientPath"; +import HOME, { EDIT_POST } from "@/const/clientPath"; import DeleteEditDropdown from "../DeleteEditDropdown"; type PostCardOptionDropdownProps = { @@ -17,6 +14,7 @@ const PostCardOptionDropdown = ({ filePk, }: PostCardOptionDropdownProps) => { const router = useRouter(); + const { mutateAsync: deletePost } = useDeletePostMutation(); const { mutateAsync: deleteFile } = useDeleteAttachMutation(); @@ -27,8 +25,11 @@ const PostCardOptionDropdown = ({ router.push(HOME); } }; + const editHandler = () => { + router.push(EDIT_POST(String(postId))); + }; - return ; + return ; }; export default PostCardOptionDropdown; diff --git a/client/src/const/clientPath.ts b/client/src/const/clientPath.ts index f6c329a..b51e157 100644 --- a/client/src/const/clientPath.ts +++ b/client/src/const/clientPath.ts @@ -23,12 +23,12 @@ export const USER_PAGE = (pk: string | number) => `${MY_PROFILE}/${pk}`; /** * 유저가 팔로잉/팔로워 리스트페이지로 이동하는 라우트 */ -export const USER_FOLLOW_LIST = `${MY_PROFILE}/follow-list` +export const USER_FOLLOW_LIST = `${MY_PROFILE}/follow-list`; /** * 유저정보 세팅 페이지로 이동하는 라우트 */ -export const SETTING_PAGE = `${MY_PROFILE}/setting` as const +export const SETTING_PAGE = `${MY_PROFILE}/setting` as const; /** * 술과사전 페이지 라우트 @@ -69,4 +69,9 @@ export const POST_DETAIL = (userId: string, postId: string) => { */ export const NEW_POST = "/new-post"; +/** + * 포스트ID 를 입력받아 해당 포스트틀 수정하는 링크로 이동 + */ +export const EDIT_POST = (postId: string) => `/edit-post/${postId}`; + export default HOME; diff --git a/client/src/const/serverPath.ts b/client/src/const/serverPath.ts index b02d8ea..85d9a98 100644 --- a/client/src/const/serverPath.ts +++ b/client/src/const/serverPath.ts @@ -52,6 +52,12 @@ export const POST_LIST_V2 = "/posts/v2" as const; * ID(pk) 를 입력받아 해당 포스트를 지우는 URL */ export const REMOVE_POST = (pk: number) => `${POST_LIST}/${pk}` as const; + +/** + * ID(pk) 를 입력받아 해당 포스트를 수정하는 URL + */ +export const EDIT_POST = (pk: string) => `${POST_LIST}/${pk}` as const; + /** * 포스트의 PK를 입력받아 해당 PK의 게시글의 좋아요를 요청 * @param id 게시글의 PK diff --git a/client/src/queries/newPost/useEditPostMutation.ts b/client/src/queries/newPost/useEditPostMutation.ts new file mode 100644 index 0000000..6ff3593 --- /dev/null +++ b/client/src/queries/newPost/useEditPostMutation.ts @@ -0,0 +1,36 @@ +import { useMutation } from "@tanstack/react-query"; +import { EDIT_POST } from "@/const/serverPath"; +import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface"; +import { useErrorHandler } from "@/utils/errorHandler"; +import useAxiosPrivate from "@/hooks/useAxiosPrivate"; + +const useEditPostMutation = () => { + const errorHandler = useErrorHandler(); + return useMutation({ + mutationFn: async ({ + pk, + formData, + }: { + pk: string; + formData: NewPostRequestInterface; + }) => { + const data = await editPostFn(pk, formData); + return data; + }, + onError: (err) => errorHandler(err), + }); +}; + +export const editPostFn = async ( + pk: string, + formData: NewPostRequestInterface +) => { + const axiosPrivate = useAxiosPrivate(); + const { data } = await axiosPrivate.patch( + EDIT_POST(pk), + formData + ); + return data; +}; + +export default useEditPostMutation; diff --git a/client/src/queries/newPost/useNewPostMutation.tsx b/client/src/queries/newPost/useNewPostMutation.ts similarity index 100% rename from client/src/queries/newPost/useNewPostMutation.tsx rename to client/src/queries/newPost/useNewPostMutation.ts diff --git a/client/src/queries/newPost/useSubmitEditPostMutation.tsx b/client/src/queries/newPost/useSubmitEditPostMutation.tsx new file mode 100644 index 0000000..0d61489 --- /dev/null +++ b/client/src/queries/newPost/useSubmitEditPostMutation.tsx @@ -0,0 +1,82 @@ +import { POST_IMAGE_SIZE } from "@/const/imageSize"; +import useNewAttachMutation from "@/queries/attach/useNewAttachMutation"; +import { useGlobalSnackbarStore } from "@/store/useGlobalSnackbarStore"; +import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface"; +import { UseMutationOptions, useMutation } from "@tanstack/react-query"; +import useEditPostMutation from "./useEditPostMutation"; +import useDeleteAttachMutation from "../attach/useDeleteAttachMutation"; + +const useSubmitEditPostMutation = ( + options?: UseMutationOptions< + void, + Error, + { + formData: NewPostRequestInterface; + file?: File | undefined; + }, + void + > +) => { + const { mutateAsync: editPostHandler } = useEditPostMutation(); + const { mutateAsync: attachFileHandler } = useNewAttachMutation(); + const { mutateAsync: deleteFileHandler } = useDeleteAttachMutation(); + + const fireToast = useGlobalSnackbarStore(({ fireToast }) => fireToast); + + return useMutation({ + mutationFn: async ({ + postNo, + formData, + file, + prevFileNo, + }: { + postNo: string; + formData: NewPostRequestInterface; + file?: File; + prevFileNo?: string; + }) => { + if (!formData.postContent?.length) { + fireToast("내용을 입력해주세요"); + return; + } + // 게시글 수정시도 + try { + await editPostHandler({ pk: postNo, formData }); + } catch (error) { + console.error("게시글수정 실패:", error); + return; + } + // 게시글 수정 성공시 파일 수정시도 + if (file) { + try { + await handleFileAttachments(file, postNo, prevFileNo); + } catch (error) { + console.error("파일처리 실패:", error); + } + } + }, + ...options, + }); + + async function handleFileAttachments( + file: File, + postNo: string, + prevFileNo?: string + ) { + try { + await attachFileHandler({ + file, + url: { pk: Number(postNo), type: "POST" }, + size: POST_IMAGE_SIZE, + }); + } catch (error) { + console.log("파일 첨부에 실패:", error); + } + + if (prevFileNo) { + await deleteFileHandler(prevFileNo); + } + } +}; + +export default useSubmitEditPostMutation; diff --git a/client/src/queries/newPost/useSubmitPostMutation.ts b/client/src/queries/newPost/useSubmitPostMutation.ts new file mode 100644 index 0000000..bbbe0fe --- /dev/null +++ b/client/src/queries/newPost/useSubmitPostMutation.ts @@ -0,0 +1,75 @@ +import { POST_IMAGE_SIZE } from "@/const/imageSize"; +import useNewAttachMutation from "@/queries/attach/useNewAttachMutation"; +import useNewPostMutation from "@/queries/newPost/useNewPostMutation"; +import { useDeletePostMutation } from "@/queries/post/useDeletePostMutation"; +import { useGlobalSnackbarStore } from "@/store/useGlobalSnackbarStore"; +import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface"; +import { UseMutationOptions, useMutation } from "@tanstack/react-query"; + +/** + * 파일, 포스트내용을 입력받아 서버에 제출하는 트렌젝션을 수행하는 뮤테이션, + * @param Options mutate, success, settle 시 실행될 함수 + * @returns 파일, 포스트내용을 입력받아 제출트렌젝션을 수행하는 함수 + */ +const useSubmitPostMutation = ( + options?: UseMutationOptions< + void, + Error, + { + formValue: NewPostRequestInterface; + file?: File | undefined; + }, + void + > +) => { + const { mutateAsync: newPostHandler } = useNewPostMutation(); + const { mutateAsync: attachFileHandler } = useNewAttachMutation(); + const { mutateAsync: deletePostHandler } = useDeletePostMutation(); + const fireToast = useGlobalSnackbarStore(({ fireToast }) => fireToast); + + return useMutation({ + mutationFn: async ({ + formValue, + file, + }: { + formValue: NewPostRequestInterface; + file?: File; + }) => { + if (!formValue.postContent?.length) { + fireToast("내용을 입력해주세요"); + return; + } + let postNo; + try { + const { postNo: res } = await newPostHandler(formValue); + postNo = res; + } catch (error) { + console.log("게시글 처리 실패 처리 실패", error); + return; + } + if (file) { + try { + await fileHandler(file, postNo); + } catch (error) { + console.log("파일 처리 실패", error); + } + } + }, + ...options, + }); + + async function fileHandler(file: File, postNo: number) { + try { + await attachFileHandler({ + file, + url: { pk: postNo, type: "POST" }, + size: POST_IMAGE_SIZE, + }); + } catch { + deletePostHandler(postNo); + return; + } + } +}; + +export default useSubmitPostMutation; diff --git a/client/src/queries/post/useGetPostDetailQuery.tsx b/client/src/queries/post/useGetPostDetailQuery.tsx index 6296303..658a9ef 100644 --- a/client/src/queries/post/useGetPostDetailQuery.tsx +++ b/client/src/queries/post/useGetPostDetailQuery.tsx @@ -1,4 +1,4 @@ -import { useSuspenseQuery } from "@tanstack/react-query"; +import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; import axios from "@/libs/axios"; import { PostInterface } from "@/types/post/PostInterface"; import { AxiosRequestConfig } from "axios"; @@ -35,4 +35,10 @@ export const postDetailQueryKey = { byId: (id: string) => ["post", id] as const, }; +export const useInvalidatePostDetail = () => { + const queryclient = useQueryClient(); + return (id: string) => + queryclient.invalidateQueries({ queryKey: postDetailQueryKey.byId(id) }); +}; + export default useGetPostDetailQuery; diff --git a/client/src/utils/formatTime.ts b/client/src/utils/formatTime.ts index c69b962..8cce193 100644 --- a/client/src/utils/formatTime.ts +++ b/client/src/utils/formatTime.ts @@ -13,6 +13,13 @@ const formatTime = (timestamp: string, now?: string) => { const postTime = dayjs(timestamp); const currentTime = dayjs(now || new Date()); + if ( + currentTime.diff(postTime, "day") < 7 && + currentTime.diff(postTime, "day") >= 1 + ) { + return `${currentTime.diff(postTime, "day")}일 전`; + } + // 1주 ~ 1달 까지는 n주 전 으로 표기 if ( currentTime.diff(postTime, "day") >= 7 &&