From ef701a3c173393add448ee26ffbfe95d0c98471a Mon Sep 17 00:00:00 2001 From: Jungu Lee <1zzangjun@gmail.com> Date: Mon, 20 Nov 2023 08:37:57 +0900 Subject: [PATCH 1/2] =?UTF-8?q?New=20:=20=EA=B8=80=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/post/PostCard.tsx | 11 +++--- .../post/PostCardOptionDropdown.tsx | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 client/src/components/post/PostCardOptionDropdown.tsx diff --git a/client/src/components/post/PostCard.tsx b/client/src/components/post/PostCard.tsx index e9bf95a..dfff357 100644 --- a/client/src/components/post/PostCard.tsx +++ b/client/src/components/post/PostCard.tsx @@ -1,7 +1,7 @@ "use client"; import { PostInterface } from "@/types/post/PostInterface"; -import { MoreVertOutlined } from "@mui/icons-material"; + import { Box, Card, @@ -28,6 +28,7 @@ import UserAvatar from "../user/info/UserAvatar"; import Link from "next/link"; import { USER_PAGE } from "@/const/clientPath"; import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery"; +import PostCardOptionDropdown from "./PostCardOptionDropdown"; const PostCard = ({ postAttachUrls, @@ -56,7 +57,6 @@ const PostCard = ({ () => currentUser?.userNo === createdBy, [currentUser] ); - return ( @@ -81,6 +81,7 @@ const PostCard = ({ display: "flex", flexDirection: "row", gap: 1, + height: 24, }} > {/* 타이틀 */} @@ -98,9 +99,9 @@ const PostCard = ({ - - {isMyPost && } - + {isMyPost && ( + + )} {alcoholName && ( diff --git a/client/src/components/post/PostCardOptionDropdown.tsx b/client/src/components/post/PostCardOptionDropdown.tsx new file mode 100644 index 0000000..756b5cb --- /dev/null +++ b/client/src/components/post/PostCardOptionDropdown.tsx @@ -0,0 +1,39 @@ +import React, { useState } from "react"; +import { MoreVertOutlined } from "@mui/icons-material"; +import { ButtonBase, Menu, MenuItem } from "@mui/material"; +import { useDeletePostMutation } from "@/queries/post/useDeletePostMutation"; + +type PostCardOptionDropdownProps = { + postId:number +}; + +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 handleClose = () => { + setAnchorEl(null); + }; + return ( + <> + + + + + { + if(confirm('정말 삭제하시겠습니까?')){ + deletePost(postId) + } + }}>삭제 + 수정 + + + ); +}; + +export default PostCardOptionDropdown; From 54250466adfa92f905d4e8649c6eb8122bb9401c Mon Sep 17 00:00:00 2001 From: Jungu Lee <1zzangjun@gmail.com> Date: Mon, 20 Nov 2023 10:18:45 +0900 Subject: [PATCH 2/2] =?UTF-8?q?New=20:=20=EC=8A=A4=EB=82=B5=EB=B0=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20=EB=AF=B8=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/app/layout.tsx | 2 + client/src/components/ErrorPage.tsx | 2 +- client/src/components/GlobalToast.tsx | 55 +++++++++++++++++++ client/src/components/post/PostDetail.tsx | 4 +- client/src/queries/auth/useLoginMutation.tsx | 4 +- client/src/queries/auth/useSignupMutation.tsx | 4 ++ .../queries/newPost/useNewPostMutation.tsx | 2 + .../src/queries/post/useDeletePostMutation.ts | 4 ++ .../src/queries/post/useLikePostMutation.tsx | 4 ++ .../queries/post/useUnLikePostMutation.tsx | 4 ++ client/src/queries/user/useFollowMutation.ts | 4 +- .../src/queries/user/useUnFollowMutation.ts | 4 +- client/src/store/useGlobalSnackbarStore.ts | 21 +++++++ client/src/utils/errorHandler.ts | 13 ++++- 14 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 client/src/components/GlobalToast.tsx create mode 100644 client/src/store/useGlobalSnackbarStore.ts diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index 71e5e31..7a33854 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -9,6 +9,7 @@ import "./globals.css"; import CustomQueryClientProvider from "@/components/queryClient/CustomQueryClientProvider"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import GlobalLoadingPopup from "./../components/GlobalLoadingPopup"; +import GlobalToast from "@/components/GlobalToast"; export const metadata: Metadata = { title: `${nameOfApp} | ${oneLineMessage}`, @@ -35,6 +36,7 @@ export default function RootLayout({ children, Modal }: RootLayoutInterface) { + {Modal} void; }) => { useEffect(() => { - errorHandler(JSON.stringify(error)); + errorHandler(error); }, [error]); return ( diff --git a/client/src/components/GlobalToast.tsx b/client/src/components/GlobalToast.tsx new file mode 100644 index 0000000..371e31c --- /dev/null +++ b/client/src/components/GlobalToast.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { + SnackbarVariant, + useGlobalSnackbarStore, +} from "@/store/useGlobalSnackbarStore"; +import { CheckCircle, Error, Warning } from "@mui/icons-material"; +import { Snackbar, SnackbarContent, Stack } from "@mui/material"; + +const GlobalToast = () => { + const { isOpen, variant, message, closeToast } = useGlobalSnackbarStore(); + return ( + + } + /> + + ); +}; + +const SnackbarMessage = ({ + message, + variant, +}: { + message: string; + variant: SnackbarVariant; +}) => { + return ( + + {IconSelector(variant)} + {message} + + ); +}; + +const IconSelector = (variant: SnackbarVariant) => { + switch (variant) { + case "danger": + return ; + case "warning": + return ; + default: + return ; + } +}; + +export default GlobalToast; diff --git a/client/src/components/post/PostDetail.tsx b/client/src/components/post/PostDetail.tsx index 4ac090b..07750fc 100644 --- a/client/src/components/post/PostDetail.tsx +++ b/client/src/components/post/PostDetail.tsx @@ -3,12 +3,14 @@ import useGetPostDetailQuery from "@/queries/post/useGetPostDetailQuery"; import PostCard from "./PostCard"; import { PostInterface } from "@/types/post/PostInterface"; +import { CircularProgress } from "@mui/material"; interface PostDetailInterface { postNo: string; initialData: PostInterface; } const PostDetail = async ({ postNo, initialData }: PostDetailInterface) => { const { data } = useGetPostDetailQuery(postNo, { initialData }); - return ; + //FIXME 포스트의 좋아요갯수가 업데이트 되지않음 + return data ? : ; }; export default PostDetail; diff --git a/client/src/queries/auth/useLoginMutation.tsx b/client/src/queries/auth/useLoginMutation.tsx index 679f76c..74ba379 100644 --- a/client/src/queries/auth/useLoginMutation.tsx +++ b/client/src/queries/auth/useLoginMutation.tsx @@ -29,8 +29,8 @@ const useLoginMutation = () => { router.refresh(); router.push(HOME); }, - onError: (error: AxiosError<{ detailMessage: string }>) => - errorHandler(error.response?.data.detailMessage ?? "에러가 발생했니다"), + onError: (error) => + errorHandler(error), onSettled: () => { setLoading(false); }, diff --git a/client/src/queries/auth/useSignupMutation.tsx b/client/src/queries/auth/useSignupMutation.tsx index 26f1561..509c721 100644 --- a/client/src/queries/auth/useSignupMutation.tsx +++ b/client/src/queries/auth/useSignupMutation.tsx @@ -5,6 +5,7 @@ import { SignupRequirement } from "@/types/auth/signupRequirement"; import { useMutation } from "@tanstack/react-query"; import useLoginMutation from "./useLoginMutation"; import { useGlobalLoadingStore } from "@/store/useGlobalLoadingStore"; +import errorHandler from "@/utils/errorHandler"; const useSignupMutation = () => { const { mutate: loginHandler } = useLoginMutation(); @@ -25,6 +26,9 @@ const useSignupMutation = () => { onSettled: () => { setLoading(false); }, + onError: (err) => { + errorHandler(err); + }, }); }; diff --git a/client/src/queries/newPost/useNewPostMutation.tsx b/client/src/queries/newPost/useNewPostMutation.tsx index 4ef6ceb..494fd88 100644 --- a/client/src/queries/newPost/useNewPostMutation.tsx +++ b/client/src/queries/newPost/useNewPostMutation.tsx @@ -3,6 +3,7 @@ import axios from "@/libs/axios"; import { POST_LIST } from "@/const/serverPath"; import { NewPostRequestInterface } from "@/types/newPost/NewPostInterface"; import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; +import errorHandler from "@/utils/errorHandler"; const useNewPostMutation = () => { return useMutation({ @@ -10,6 +11,7 @@ const useNewPostMutation = () => { const data = await usePostNewPostFn(formData); return data; }, + onError: (err) => errorHandler(err), }); }; diff --git a/client/src/queries/post/useDeletePostMutation.ts b/client/src/queries/post/useDeletePostMutation.ts index b209aef..481767b 100644 --- a/client/src/queries/post/useDeletePostMutation.ts +++ b/client/src/queries/post/useDeletePostMutation.ts @@ -2,6 +2,7 @@ import { REMOVE_POST } from "@/const/serverPath"; import { axiosPrivate } from "@/libs/axios"; import { useMutation } from "@tanstack/react-query"; import { useInvalidatePostList } from "./useGetPostListInfiniteQuery"; +import errorHandler from "@/utils/errorHandler"; export const useDeletePostMutation = () => { const invalidatePreviousData = useInvalidatePostList(); @@ -10,6 +11,9 @@ export const useDeletePostMutation = () => { onSuccess: () => { invalidatePreviousData(); }, + onError:(err)=>{ + errorHandler(err) + } }); }; diff --git a/client/src/queries/post/useLikePostMutation.tsx b/client/src/queries/post/useLikePostMutation.tsx index 3c313f0..769b7a6 100644 --- a/client/src/queries/post/useLikePostMutation.tsx +++ b/client/src/queries/post/useLikePostMutation.tsx @@ -5,6 +5,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { getPostListInfiniteQueryKey } from "./useGetPostListInfiniteQuery"; import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; import { POST_LIKE_URL } from "@/const/serverPath"; +import errorHandler from "@/utils/errorHandler"; /** * 좋아요를 수행하고, 게시글을 invalidation 하는 쿼리 * @returns @@ -17,6 +18,9 @@ const useLikePostMutation = () => { queryClient.invalidateQueries({ queryKey: getPostListInfiniteQueryKey.all, }), + onError:(err)=>{ + errorHandler(err) + } }); }; diff --git a/client/src/queries/post/useUnLikePostMutation.tsx b/client/src/queries/post/useUnLikePostMutation.tsx index c6a9def..7f0f6bf 100644 --- a/client/src/queries/post/useUnLikePostMutation.tsx +++ b/client/src/queries/post/useUnLikePostMutation.tsx @@ -5,6 +5,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { getPostListInfiniteQueryKey } from "./useGetPostListInfiniteQuery"; import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage"; import { POST_UN_LIKE_URL } from "@/const/serverPath"; +import errorHandler from "@/utils/errorHandler"; /** * 좋아요를 취소하고, 게시글을 invalidation 하는 쿼리 * @returns @@ -17,6 +18,9 @@ const useLikePostMutation = () => { queryClient.invalidateQueries({ queryKey: getPostListInfiniteQueryKey.all, }), + onError:(err)=>{ + errorHandler(err) + } }); }; diff --git a/client/src/queries/user/useFollowMutation.ts b/client/src/queries/user/useFollowMutation.ts index a2c94d9..824eb49 100644 --- a/client/src/queries/user/useFollowMutation.ts +++ b/client/src/queries/user/useFollowMutation.ts @@ -6,6 +6,7 @@ import { UserInfoQueryKey } from "./useUserInfoQuery"; import { UserInfoInterface } from "@/types/user/userInfoInterface"; import { MyInfoQueryKeys } from "../auth/useMyInfoQuery"; import { MyInfoInterface } from "@/types/auth/myInfo"; +import errorHandler from "@/utils/errorHandler"; const useFollowMutation = () => { const queryClient = useQueryClient(); @@ -39,7 +40,8 @@ const useFollowMutation = () => { /** * Mutation 실패시 원래 QuerySnapShot정보로 롤백 */ - onError: (_err, queryFnParams, context) => { + onError: (err, queryFnParams, context) => { + errorHandler(err); if (!context) { return; } diff --git a/client/src/queries/user/useUnFollowMutation.ts b/client/src/queries/user/useUnFollowMutation.ts index 69bc9a7..4fc9b28 100644 --- a/client/src/queries/user/useUnFollowMutation.ts +++ b/client/src/queries/user/useUnFollowMutation.ts @@ -6,6 +6,7 @@ import { UserInfoQueryKey } from "./useUserInfoQuery"; import { UserInfoInterface } from "@/types/user/userInfoInterface"; import { MyInfoQueryKeys } from "../auth/useMyInfoQuery"; import { MyInfoInterface } from "@/types/auth/myInfo"; +import errorHandler from "@/utils/errorHandler"; const useUnFollowMutation = () => { const queryClient = useQueryClient(); @@ -42,7 +43,8 @@ const useUnFollowMutation = () => { /** * Mutation 실패시 원래 QuerySnapShot정보로 롤백 */ - onError: (_err, queryFnParams, context) => { + onError: (err, queryFnParams, context) => { + errorHandler(err) if (!context) { return; } diff --git a/client/src/store/useGlobalSnackbarStore.ts b/client/src/store/useGlobalSnackbarStore.ts new file mode 100644 index 0000000..74efec2 --- /dev/null +++ b/client/src/store/useGlobalSnackbarStore.ts @@ -0,0 +1,21 @@ +import { create } from "zustand"; + +interface GlobalSnackbarStore { + isOpen: boolean; + message: string; + variant: SnackbarVariant; + fireToast: (message: string, variant?: SnackbarVariant) => void; + closeToast:()=>void +} +export type SnackbarVariant = "neutral" | "success" | "danger" | "warning"; + +export const useGlobalSnackbarStore = create((set) => ({ + isOpen: false, + message: "", + variant: "neutral", + fireToast: (message, variant = "neutral") => + set({ isOpen: true, message, variant }), + closeToast: () => set((prev) => ({ ...prev, message: "", isOpen: false })), +})); + +export const useFireToast =()=> useGlobalSnackbarStore((state)=>state.fireToast) diff --git a/client/src/utils/errorHandler.ts b/client/src/utils/errorHandler.ts index b3ee737..09a1910 100644 --- a/client/src/utils/errorHandler.ts +++ b/client/src/utils/errorHandler.ts @@ -1,3 +1,12 @@ -export default function errorHandler(error: string) { - console.log(error); +"use client"; +// import { useGlobalSnackbarStore } from "@/store/useGlobalSnackbarStore"; +import { isAxiosError } from "axios"; + +export default function ErrorHandler(error: Error) { + // const { fireToast } = useGlobalSnackbarStore(); + if (isAxiosError(error) && error.response) { + // FIXME : Zustand 사용 연구 + // error.response.status === 401 && fireToast("로그인 후 이용 가능합니다"); + error.response.status === 401 && console.log("로그인 후 이용 가능합니다"); + } }