diff --git a/client/src/app/@Modal/(.)post/[userId]/[postId]/page.tsx b/client/src/app/@Modal/(.)post/[userId]/[postId]/page.tsx deleted file mode 100644 index 8b092e2..0000000 --- a/client/src/app/@Modal/(.)post/[userId]/[postId]/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import PostDetailPage from "@/app/post/[userId]/[postId]/page"; -import ModalWrapper from "@/components/ModalWrapper"; - -const page = async ({ params }: { params: { postId: string } }) => { - return ( - - - - ); -}; - -export default page; diff --git a/client/src/app/@Modal/layout.tsx b/client/src/app/@Modal/layout.tsx index 5aa1ad9..18e98b8 100644 --- a/client/src/app/@Modal/layout.tsx +++ b/client/src/app/@Modal/layout.tsx @@ -5,7 +5,7 @@ import { usePathname } from "next/navigation"; export default function Layout({ children }: any) { const pathname = usePathname(); - const allowedPath = ["/post/", NEW_POST, SIGNIN]; + const allowedPath = [NEW_POST, SIGNIN]; return allowedPath.some((path) => pathname.startsWith(path)) ? children diff --git a/client/src/app/post/[userId]/[postId]/page.tsx b/client/src/app/post/[userId]/[postId]/page.tsx index 4d84e5a..6002903 100644 --- a/client/src/app/post/[userId]/[postId]/page.tsx +++ b/client/src/app/post/[userId]/[postId]/page.tsx @@ -1,8 +1,10 @@ "use server"; -import PostDetail from "@/components/post/PostDetail"; +import PostDetail from "@/components/post/detail/PostDetail"; import { getPostDetailQueryFn } from "@/queries/post/useGetPostDetailQuery"; import getTokenFromCookies from "@/utils/getTokenFromCookies"; +import { Paper, Container } from "@mui/material"; import { redirect } from "next/navigation"; +import CustomAppbar from "@/components/CustomAppbar"; const PostDetailPage = async ({ params }: { params: { postId: string } }) => { const parsedPostId = params.postId; @@ -15,7 +17,22 @@ const PostDetailPage = async ({ params }: { params: { postId: string } }) => { } catch { redirect("/not-found"); } - return ; + return ( + <> + + + + + + + + ); }; export default PostDetailPage; diff --git a/client/src/assets/icons/CommentIcon.svg b/client/src/assets/icons/CommentIcon.svg index e877410..1ee8ead 100644 --- a/client/src/assets/icons/CommentIcon.svg +++ b/client/src/assets/icons/CommentIcon.svg @@ -5,6 +5,6 @@ + fill="#D9D9D9" /> \ No newline at end of file diff --git a/client/src/assets/icons/comment/SubmitCommentIcon.svg b/client/src/assets/icons/comment/SubmitCommentIcon.svg new file mode 100644 index 0000000..2f5173c --- /dev/null +++ b/client/src/assets/icons/comment/SubmitCommentIcon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/src/components/ModalWrapper.tsx b/client/src/components/ModalWrapper.tsx index d10328e..0fcb7ba 100644 --- a/client/src/components/ModalWrapper.tsx +++ b/client/src/components/ModalWrapper.tsx @@ -29,6 +29,7 @@ const ModalWrapper = ({ children, disableBox }: ModalInterface) => { alignItems: "center", justifyContent: "center", display: "flex", + position:'absolute' }} > { @@ -39,6 +40,7 @@ const ModalWrapper = ({ children, disableBox }: ModalInterface) => { p: disableBox ? 0 : 4, maxWidth: "90%", maxHeight: "90%", + overflowY:'auto' }} > <>{children} diff --git a/client/src/components/Navigation/NavigationBar.tsx b/client/src/components/Navigation/NavigationBar.tsx index fef2927..122b669 100644 --- a/client/src/components/Navigation/NavigationBar.tsx +++ b/client/src/components/Navigation/NavigationBar.tsx @@ -5,8 +5,15 @@ import HomeIcon from "~/assets/icons/HomeIcon.svg"; import SearchIcon from "~/assets/icons/SearchIcon.svg"; import PostIcon from "~/assets/icons/PostIcon.svg"; import BeverageIcon from "~/assets/icons/BeverageIcon.svg"; +import { useGlobalNavbarVisibility } from "@/store/useGlobalNavbarVisibility"; -import HOME, { MY_PROFILE, NEW_POST, SEARCH, SIGNIN, WIKI } from "@/const/clientPath"; +import HOME, { + MY_PROFILE, + NEW_POST, + SEARCH, + SIGNIN, + WIKI, +} from "@/const/clientPath"; import Link from "next/link"; import { usePathname } from "next/navigation"; import NavbarUserImage from "@/components/Navigation/NavbarUserImage"; @@ -16,6 +23,9 @@ import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery"; const NavigationBar = () => { const path = usePathname(); const { data: userInfo } = useMyInfoQuery(); + + const isVisible = useGlobalNavbarVisibility(({ isVisible }) => isVisible); + const NavbarData = useMemo( () => [ { @@ -45,35 +55,32 @@ const NavigationBar = () => { ], [userInfo] ); - return ( - - - {NavbarData.map(({ label, href, iconComponent, ...others }) => { - return ( - - ); - })} - - + return isVisible ? ( + + {NavbarData.map(({ label, href, iconComponent, ...others }) => { + return ( + + ); + })} + + ) : ( + <> ); }; -const WrapperStyle = { +const BtnStyle = { position: "fixed", bottom: 0, left: 0, right: 0, - borderRadius: 0, -}; -const BtnStyle = { borderRadius: "12px 12px 0 0", border: "1px solid", borderBottom: "none", diff --git a/client/src/components/post/PostCard.tsx b/client/src/components/post/PostCard.tsx index 62b9a15..6ef4f5b 100644 --- a/client/src/components/post/PostCard.tsx +++ b/client/src/components/post/PostCard.tsx @@ -18,7 +18,6 @@ import { useContext, useMemo } from "react"; import ShareIcon from "@/assets/icons/ShareIcon.svg"; import LikeIcon from "@/assets/icons/LikeIcon.svg"; import CommentIcon from "@/assets/icons/CommentIcon.svg"; -import QuoteIcon from "@/assets/icons/QuoteIcon.svg"; import AlcoholNameTag from "@/components/wiki/AlcoholNameTag"; import dayjs from "dayjs"; import useLikePostMutation from "@/queries/post/useLikePostMutation"; @@ -30,7 +29,7 @@ import Link from "next/link"; import { USER_PAGE } from "@/const/clientPath"; import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery"; import PostCardOptionDropdown from "./PostCardOptionDropdown"; -import { postcardContext } from "@/store/PostCardContext"; +import { postcardContext } from "@/store/post/PostCardContext"; const PostCard = ({ postAttachUrls, @@ -47,7 +46,6 @@ const PostCard = ({ alcoholType, commentCount, likedByMe, - quoteCount, alcoholNo, }: PostInterface) => { const openPostDetailPage = useOpenPostDetailPage(); @@ -163,10 +161,6 @@ const PostCard = ({ {likeCount ?? 0} - - - {quoteCount ?? 0} - 공유 diff --git a/client/src/components/post/PostCardList.tsx b/client/src/components/post/PostCardList.tsx index 0fd8999..d5f4541 100644 --- a/client/src/components/post/PostCardList.tsx +++ b/client/src/components/post/PostCardList.tsx @@ -6,12 +6,13 @@ import useGetPostListInfiniteQuery, { } from "@/queries/post/useGetPostListInfiniteQuery"; import { useInView } from "react-intersection-observer"; import { useEffect } from "react"; -import { Box, CircularProgress, Stack } from "@mui/material"; +import { Stack } from "@mui/material"; import { useMemo } from "react"; import Image from "next/image"; import NoResult from "@/assets/images/noResult.png"; import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; -import { postcardContext } from "@/store/PostCardContext"; +import { postcardContext } from "@/store/post/PostCardContext"; +import PostCardSkeleton from "./PostCardSkeleton"; function PostCardList(props: UseGetPostListQueryInterface) { const { @@ -28,7 +29,7 @@ function PostCardList(props: UseGetPostListQueryInterface) { const { searchKeyword, searchUserNos } = props; - const { ref, inView } = useInView({ threshold: 0.9 }); + const { ref, inView } = useInView(); useEffect(() => { if (hasNextPage && inView) fetchNextPage(); }, [inView, hasNextPage]); @@ -55,9 +56,7 @@ function PostCardList(props: UseGetPostListQueryInterface) { )} {/* 로딩창 */} {isFetchingNextPage || isLoading ? ( - - - + ) : ( // 인터섹션옵저버
diff --git a/client/src/components/post/PostCardOptionDropdown.tsx b/client/src/components/post/PostCardOptionDropdown.tsx index 756b5cb..53b39ce 100644 --- a/client/src/components/post/PostCardOptionDropdown.tsx +++ b/client/src/components/post/PostCardOptionDropdown.tsx @@ -4,17 +4,17 @@ import { ButtonBase, Menu, MenuItem } from "@mui/material"; import { useDeletePostMutation } from "@/queries/post/useDeletePostMutation"; type PostCardOptionDropdownProps = { - postId:number + postId: number; }; -const PostCardOptionDropdown = ({postId}: PostCardOptionDropdownProps) => { +const PostCardOptionDropdown = ({ postId }: PostCardOptionDropdownProps) => { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; -const {mutate:deletePost}=useDeletePostMutation() + const { mutate: deletePost } = useDeletePostMutation(); const handleClose = () => { setAnchorEl(null); @@ -25,11 +25,15 @@ const {mutate:deletePost}=useDeletePostMutation()
- { - if(confirm('정말 삭제하시겠습니까?')){ - deletePost(postId) - } - }}>삭제 + { + if (confirm("정말 삭제하시겠습니까?")) { + deletePost(postId); + } + }} + > + 삭제 + 수정 diff --git a/client/src/components/post/PostCardSkeleton.tsx b/client/src/components/post/PostCardSkeleton.tsx new file mode 100644 index 0000000..f6f031a --- /dev/null +++ b/client/src/components/post/PostCardSkeleton.tsx @@ -0,0 +1,35 @@ +import { Box, Card, Skeleton, Stack } from "@mui/material"; + +const PostCardSkeleton = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default PostCardSkeleton; diff --git a/client/src/components/post/PostComment.tsx b/client/src/components/post/PostComment.tsx deleted file mode 100644 index 52b4d3e..0000000 --- a/client/src/components/post/PostComment.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { USER_PAGE } from "@/const/clientPath"; -import { Stack, Avatar, Typography } from "@mui/material"; -import dayjs from "dayjs"; -import Link from "next/link"; - -type Props = { - content: string; - nickname: string; - userId: string; - userPk: string; - createdAt: string; -}; - -const PostComment = ({ - content, - nickname, - userId, - createdAt, - userPk, -}: Props) => { - return ( - - - - - - {nickname} - - - {`@${userId}`} - - - {dayjs(createdAt).format("MM.DD")} - - - {content} - - - ); -}; - -export default PostComment; diff --git a/client/src/components/post/PostCommentList.tsx b/client/src/components/post/PostCommentList.tsx deleted file mode 100644 index 75f73dd..0000000 --- a/client/src/components/post/PostCommentList.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import PostComment from "./PostComment"; -import { Card, Stack } from "@mui/material"; - -type Props = { - postNo: string; -}; - -const PostCommentList = ({ postNo }: Props) => { - const comments = Array.from(new Array(4)).map((_e, i) => ({ - commentNo: i, - comment: - "Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus laudantium enim veritatis minus excepturi. Quidem iusto, velit facilis corporis at suscipit minima sed, cumque nostrum fugiat ad natus? Voluptatum, odio?", - createdAt: new Date(), - nickname: "유저이름", - userId: "유저유저아이디", - })); - return ( - comments && - comments.length > 0 && ( - - - {comments.map( - ({ commentNo, comment, createdAt, nickname, userId }, i) => ( - - ) - )} - - - ) - ); -}; - -export default PostCommentList; diff --git a/client/src/components/post/PostDetail.tsx b/client/src/components/post/PostDetail.tsx deleted file mode 100644 index 637f760..0000000 --- a/client/src/components/post/PostDetail.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client"; - -import useGetPostDetailQuery from "@/queries/post/useGetPostDetailQuery"; -import PostCard from "./PostCard"; -import { PostInterface } from "@/types/post/PostInterface"; -import { CircularProgress } from "@mui/material"; -import PostCommentList from "@/components/post/PostCommentList"; -import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; -interface PostDetailInterface { - postNo: string; - initialData: PostInterface; -} -const PostDetail = ({ postNo, initialData }: PostDetailInterface) => { - const { data } = useGetPostDetailQuery(postNo, { - initialData, - headers: { Authorization: getTokenFromLocalStorage() }, - }); - //FIXME 포스트의 좋아요갯수가 업데이트 되지않음 - return ( - <> - {data ? ( - <> - - - - ) : ( - - )} - - ); -}; -export default PostDetail; diff --git a/client/src/components/post/detail/PostComment.tsx b/client/src/components/post/detail/PostComment.tsx new file mode 100644 index 0000000..3e21efc --- /dev/null +++ b/client/src/components/post/detail/PostComment.tsx @@ -0,0 +1,61 @@ +import UserAvatar from "@/components/user/info/UserAvatar"; +import { USER_PAGE } from "@/const/clientPath"; +import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery"; +import { Stack, Avatar, Typography } from "@mui/material"; +import dayjs from "dayjs"; +import Link from "next/link"; +import PostCommentDropdown from "./PostCommentDropdown"; + +type Props = { + content: string; + nickname: string; + userId: string; + userPk: string; + profileImg?: string; + createdAt: string; +}; + +const PostComment = ({ + content, + nickname, + userId, + createdAt, + profileImg, + userPk, +}: Props) => { + const { data: myData } = useMyInfoQuery(); + + const isMyComment = userPk === String(myData?.userNo); + + return ( + + + + + + + {nickname} + + + {`@${userId}`} + + + {dayjs(createdAt).format("MM.DD")} + + + {isMyComment && } + + {content} + + + ); +}; + +export default PostComment; diff --git a/client/src/components/post/detail/PostCommentDropdown.tsx b/client/src/components/post/detail/PostCommentDropdown.tsx new file mode 100644 index 0000000..734c0c4 --- /dev/null +++ b/client/src/components/post/detail/PostCommentDropdown.tsx @@ -0,0 +1,38 @@ +import { ButtonBase, Menu, MenuItem } from "@mui/material"; +import { MoreVertOutlined } from "@mui/icons-material"; +import { useState } from "react"; + +const PostCommentDropdown = () => { + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + { + if (confirm("정말 삭제하시겠습니까?")) { + console.log("눌림"); + } + }} + > + 삭제 + + 수정 + + + ); +}; + +export default PostCommentDropdown; diff --git a/client/src/components/post/detail/PostCommentInput.tsx b/client/src/components/post/detail/PostCommentInput.tsx new file mode 100644 index 0000000..328eafe --- /dev/null +++ b/client/src/components/post/detail/PostCommentInput.tsx @@ -0,0 +1,83 @@ +"use client"; +import { InputAdornment, Paper, TextField } from "@mui/material"; +import { useCallback, useContext, useState } from "react"; +import SubmitCommentIcon from "@/assets/icons/comment/SubmitCommentIcon.svg"; +import { useGlobalNavbarVisibility } from "@/store/useGlobalNavbarVisibility"; +import PostDetailPageContext from "@/store/post/PostDetailPageContext"; +import useNewPostCommentMutation from "@/queries/post/comment/useNewPostCommentMutation"; + +const PostCommentInput = () => { + const setIsShowingNavbar = useGlobalNavbarVisibility( + ({ setIsVisible }) => setIsVisible + ); + const { data: currentData } = useContext(PostDetailPageContext); + const [isEditing, setIsEditing] = useState(false); + const [inputValue, setInputValue] = useState(""); + + const { mutateAsync: submitForm } = useNewPostCommentMutation( + currentData?.postNo ? String(currentData?.postNo) : undefined + ); + const submitHandler = useCallback( + (content: string) => { + submitForm(content).then(() => { + setInputValue(""); + }); + }, + [submitForm, setInputValue] + ); + + return ( + + { + setIsShowingNavbar(false); + setIsEditing(true); + }} + onBlur={() => { + setIsShowingNavbar(true); + setIsEditing(false); + }} + size="small" + autoComplete="off" + placeholder="회원님의 생각을 올려보세요" + multiline + value={inputValue} + onChange={(e) => setInputValue(e.target.value)} + rows={isEditing ? 5 : 1} + InputProps={{ + endAdornment: ( + { + e.stopPropagation(); + submitHandler(inputValue) + }} + sx={{ + color: inputValue.length > 0 ? "primary.main" : "text.disabled", + }} + > + + + ), + }} + sx={{ backgroundColor: "background.paper" }} + /> + + ); +}; + +export default PostCommentInput; diff --git a/client/src/components/post/detail/PostCommentList.tsx b/client/src/components/post/detail/PostCommentList.tsx new file mode 100644 index 0000000..e9c71f1 --- /dev/null +++ b/client/src/components/post/detail/PostCommentList.tsx @@ -0,0 +1,46 @@ +import useGetCommentQuery from "@/queries/post/comment/useGetCommentQuery"; +import PostComment from "./PostComment"; +import { Card, Stack } from "@mui/material"; + +type Props = { + postNo: string; +}; + +const PostCommentList = ({ postNo }: Props) => { + const { data: comments } = useGetCommentQuery({ postNo }); + + return comments.list.length > 0 ? ( + + + {comments.list.map( + ( + { + commentNo, + commentContent, + createdDate, + createdBy, + nickname, + userId, + profileImgUrls, + }, + i + ) => ( + + ) + )} + + + ) : ( + <> + ); +}; + +export default PostCommentList; diff --git a/client/src/components/post/detail/PostCommentSkeleton.tsx b/client/src/components/post/detail/PostCommentSkeleton.tsx new file mode 100644 index 0000000..fbea0bc --- /dev/null +++ b/client/src/components/post/detail/PostCommentSkeleton.tsx @@ -0,0 +1,22 @@ +import { Skeleton, Stack, Card } from "@mui/material"; + +const PostCommentSkeleton = () => { + return ( + + + + + + + + + + + + + + + ); +}; + +export default PostCommentSkeleton; diff --git a/client/src/components/post/detail/PostDetail.tsx b/client/src/components/post/detail/PostDetail.tsx new file mode 100644 index 0000000..ab143e7 --- /dev/null +++ b/client/src/components/post/detail/PostDetail.tsx @@ -0,0 +1,34 @@ +"use client"; + +import useGetPostDetailQuery from "@/queries/post/useGetPostDetailQuery"; +import PostCard from "@/components/post/PostCard"; +import { PostInterface } from "@/types/post/PostInterface"; +import PostCommentList from "@/components/post/detail/PostCommentList"; +import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; +import { Suspense } from "react"; +import PostCommentSkeleton from "@/components/post/detail/PostCommentSkeleton"; +import PostDetailPageContext from "@/store/post/PostDetailPageContext"; +import PostCommentInput from "./PostCommentInput"; + +export interface PostDetailInterface { + postNo: string; + initialData: PostInterface; +} + +const PostDetail = ({ postNo, initialData }: PostDetailInterface) => { + const { data } = useGetPostDetailQuery(postNo, { + initialData, + headers: { Authorization: getTokenFromLocalStorage() }, + }); + + return ( + + + }> + + + + + ); +}; +export default PostDetail; diff --git a/client/src/const/serverPath.ts b/client/src/const/serverPath.ts index fe1135f..15aa942 100644 --- a/client/src/const/serverPath.ts +++ b/client/src/const/serverPath.ts @@ -30,6 +30,10 @@ export const LOGOUT_BFF = "/api/auth/logout-internal" as const; * 게시물리스트를 받아오거나, 작성하는 Path */ export const POST_LIST = "/posts" as const; +/** + * 게시물 pk 를 입력받아 댓글을 조회,생성 하는 URL + */ +export const POST_COMMENT = (pk:string) => `${POST_LIST}/${pk}/comments` /** * 게시물리스트를 받아오거나, 작성하는 Path 버전2 (Breaking Change) @@ -39,7 +43,11 @@ export const POST_LIST_V2 = "/posts/v2" as const; * ID(pk) 를 입력받아 해당 포스트를 지우는 URL */ export const REMOVE_POST = (pk: number) => `${POST_LIST}/${pk}` as const; - +/** + * 포스트의 PK를 입력받아 해당 PK의 게시글의 좋아요를 요청 + * @param id 게시글의 PK + */ +export const POST_LIKE_URL = (id: string) => `/posts/like/${id}` as const; /** * * @param type : 리소스의 타입 POST|PROFILE|ALCOHOL @@ -63,11 +71,7 @@ export const REMOVE_FILE = (attachNo: string) => `/attach/${attachNo}` as const; */ export const GET_ALCOHOL_LIST = "/alcohols" as const; -/** - * 포스트의 PK를 입력받아 해당 PK의 게시글의 좋아요를 요청 - * @param id 게시글의 PK - */ -export const POST_LIKE_URL = (id: string) => `/posts/like/${id}` as const; + /** * 포스트의 PK를 입력받아 해당 PK의 게시글의 좋아요 취소를 요청 diff --git a/client/src/hooks/useOpenPostDetailPage.tsx b/client/src/hooks/useOpenPostDetailPage.tsx index 5aea94c..fbb2b77 100644 --- a/client/src/hooks/useOpenPostDetailPage.tsx +++ b/client/src/hooks/useOpenPostDetailPage.tsx @@ -1,5 +1,5 @@ import { POST_DETAIL } from "@/const/clientPath"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useCallback } from "react"; /** @@ -8,8 +8,14 @@ import { useCallback } from "react"; */ export const useOpenPostDetailPage = () => { const router = useRouter(); + const path = usePathname(); + const openPostDetailPage = useCallback((userId: string, id: string) => { - router.push(POST_DETAIL(String(userId), String(id))); + + if (path !== POST_DETAIL(String(userId), String(id))) { + router.push(POST_DETAIL(String(userId), String(id))); + } }, []); + return openPostDetailPage; }; diff --git a/client/src/queries/post/comment/useGetCommentQuery.ts b/client/src/queries/post/comment/useGetCommentQuery.ts new file mode 100644 index 0000000..a1d8704 --- /dev/null +++ b/client/src/queries/post/comment/useGetCommentQuery.ts @@ -0,0 +1,29 @@ +import { POST_COMMENT } from "@/const/serverPath"; +import axios from "@/libs/axios"; +import PostCommentListInterface from "@/types/post/PostCommentInterface"; +import { useSuspenseQuery } from "@tanstack/react-query"; + +interface CommentQueryInterface { + postNo: string; +} + +const useGetCommentQuery = ({ postNo }: CommentQueryInterface) => { + return useSuspenseQuery({ + queryKey: commentQueryKey.byId(postNo), + queryFn: async () => await getCommentListQueryFn(postNo), + }); +}; + +export const getCommentListQueryFn = async ( + id: CommentQueryInterface["postNo"] +) => { + const { data } = await axios.get(POST_COMMENT(id)); + return data; +}; + +export const commentQueryKey = { + all: ["comment"] as const, + byId: (id?: string) => ["comment", { id }] as const, +}; + +export default useGetCommentQuery; diff --git a/client/src/queries/post/comment/useNewPostCommentMutation.ts b/client/src/queries/post/comment/useNewPostCommentMutation.ts new file mode 100644 index 0000000..789bfe9 --- /dev/null +++ b/client/src/queries/post/comment/useNewPostCommentMutation.ts @@ -0,0 +1,71 @@ +import { POST_COMMENT } from "@/const/serverPath"; +import useAxiosPrivate from "@/hooks/useAxiosPrivate"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { commentQueryKey } from "./useGetCommentQuery"; +import PostCommentListInterface from "@/types/post/PostCommentInterface"; +import { useErrorHandler } from "@/utils/errorHandler"; +import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery"; + +const useNewPostCommentMutation = (id?: string) => { + const queryClient = useQueryClient(); + const errorHandler = useErrorHandler(); + const { data: myInfo } = useMyInfoQuery(); + + return useMutation({ + mutationFn: async (content: string) => { + if (!id) { + return Promise.reject("id가 제공되지않음"); + } else return postComment(id, content); + }, + onMutate: (content) => { + queryClient.cancelQueries({ queryKey: commentQueryKey.byId(id) }); + + const querySnapShot = queryClient.getQueryData( + commentQueryKey.byId(id) + ); + + queryClient.setQueryData( + commentQueryKey.byId(id), + (prev) => { + return { + list: [ + { + commentNo: Number.MAX_SAFE_INTEGER, + commentContent: content, + createdDate: String(new Date()), + lastModifiedDate: String(new Date()), + createdBy: myInfo?.nickname, + userId: myInfo?.id, + nickname: myInfo?.nickname, + profileImgUrls: myInfo?.profileImages, + }, + ...(prev?.list ?? []), + ] as PostCommentListInterface["list"], + totalCount: (prev?.totalCount ?? 0) + 1, + }; + } + ); + return { querySnapShot }; + }, + onError: (err, queryFnParams, context) => { + errorHandler(err); + queryClient.setQueryData( + commentQueryKey.byId(queryFnParams), + context?.querySnapShot + ); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: commentQueryKey.byId(id) }); + }, + }); +}; +export const postComment = async (postNo: string, content: string) => { + const axiosPrivate = useAxiosPrivate(); + const { data } = await axiosPrivate.post<{ commentNo: string }>( + POST_COMMENT(postNo), + { commentContent: content } + ); + return data; +}; + +export default useNewPostCommentMutation; diff --git a/client/src/queries/post/useGetPostDetailQuery.tsx b/client/src/queries/post/useGetPostDetailQuery.tsx index 8916f46..6296303 100644 --- a/client/src/queries/post/useGetPostDetailQuery.tsx +++ b/client/src/queries/post/useGetPostDetailQuery.tsx @@ -1,4 +1,4 @@ -import { useQuery } from "@tanstack/react-query"; +import { useSuspenseQuery } from "@tanstack/react-query"; import axios from "@/libs/axios"; import { PostInterface } from "@/types/post/PostInterface"; import { AxiosRequestConfig } from "axios"; @@ -10,7 +10,7 @@ interface PostdetailOption { } const useGetPostDetailQuery = (postId: string, options?: PostdetailOption) => { - return useQuery({ + return useSuspenseQuery({ queryKey: postDetailQueryKey.byId(postId), queryFn: () => getPostDetailQueryFn(postId, options?.headers), initialData: options?.initialData, @@ -24,8 +24,7 @@ export const getPostDetailQueryFn = async ( const { data } = await axios.get(`/posts/${postId}`, { baseURL: process.env.NEXT_PUBLIC_BASE_URL, headers: { - Authorization: - options?.Authorization || getTokenFromLocalStorage(), + Authorization: options?.Authorization || getTokenFromLocalStorage(), }, }); return data; diff --git a/client/src/queries/post/useLikePostMutation.tsx b/client/src/queries/post/useLikePostMutation.tsx index f39908d..2fee56f 100644 --- a/client/src/queries/post/useLikePostMutation.tsx +++ b/client/src/queries/post/useLikePostMutation.tsx @@ -13,7 +13,7 @@ import { import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; import { POST_LIKE_URL } from "@/const/serverPath"; import { useErrorHandler } from "@/utils/errorHandler"; -import { PostcardContextInterface } from "@/store/PostCardContext"; +import { PostcardContextInterface } from "@/store/post/PostCardContext"; import { useOptimisticUpdatePostList } from "@/queries/post/updator/useOptimisticUpdatePostList"; import { useOptimisticUpdatePostDetail } from "./updator/useOptimisticUpdatePostDetail"; import { postDetailQueryKey } from "./useGetPostDetailQuery"; diff --git a/client/src/queries/post/useUnLikePostMutation.tsx b/client/src/queries/post/useUnLikePostMutation.tsx index abf74a8..661d13f 100644 --- a/client/src/queries/post/useUnLikePostMutation.tsx +++ b/client/src/queries/post/useUnLikePostMutation.tsx @@ -13,7 +13,7 @@ import { import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; import { POST_UN_LIKE_URL } from "@/const/serverPath"; import { useErrorHandler } from "@/utils/errorHandler"; -import { PostcardContextInterface } from "@/store/PostCardContext"; +import { PostcardContextInterface } from "@/store/post/PostCardContext"; import { useOptimisticUpdatePostList } from "@/queries/post/updator/useOptimisticUpdatePostList"; import { useOptimisticUpdatePostDetail } from "./updator/useOptimisticUpdatePostDetail"; import { postDetailQueryKey } from "./useGetPostDetailQuery"; diff --git a/client/src/store/PostCardContext.ts b/client/src/store/post/PostCardContext.ts similarity index 100% rename from client/src/store/PostCardContext.ts rename to client/src/store/post/PostCardContext.ts diff --git a/client/src/store/post/PostDetailPageContext.ts b/client/src/store/post/PostDetailPageContext.ts new file mode 100644 index 0000000..2072d0f --- /dev/null +++ b/client/src/store/post/PostDetailPageContext.ts @@ -0,0 +1,12 @@ +import { PostInterface } from "@/types/post/PostInterface"; +import { createContext } from "react"; + +interface PostDetailPageContextInterface { + data?: PostInterface; +} + +const PostDetailPageContext = createContext({ + data: undefined, +}); + +export default PostDetailPageContext; diff --git a/client/src/store/useGlobalNavbarVisibility.ts b/client/src/store/useGlobalNavbarVisibility.ts new file mode 100644 index 0000000..070ea60 --- /dev/null +++ b/client/src/store/useGlobalNavbarVisibility.ts @@ -0,0 +1,15 @@ +import { create } from "zustand"; + +interface GlobalNavbarVisibility { + isVisible: boolean; + setIsVisible: (val: boolean) => void; +} +/** + * 네비게이션바 (바텀네비게이션)을 표시할지 여부 + */ +export const useGlobalNavbarVisibility = create( + (set) => ({ + isVisible: true, + setIsVisible: (val) => set(() => ({ isVisible: val })), + }) +); diff --git a/client/src/types/post/PostCommentInterface.ts b/client/src/types/post/PostCommentInterface.ts new file mode 100644 index 0000000..5f0091e --- /dev/null +++ b/client/src/types/post/PostCommentInterface.ts @@ -0,0 +1,19 @@ +import { ProfileImagesType } from "../user/userInfoInterface"; + +interface PostCommentListInterface { + list: PostCommentInterface[]; + totalCount: number; +} + +export interface PostCommentInterface { + commentNo: number; + commentContent: string; + createdDate: string; + lastModifiedDate: string; + createdBy: number; + userId: string; + nickname: string; + profileImgUrls: ProfileImagesType[]; +} + +export default PostCommentListInterface;