diff --git a/src/App.tsx b/src/App.tsx index fe1a7c27..58e7e269 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -83,6 +83,10 @@ const App = () => { path={navigatePath.BOOKMARK_REPORT} element={} /> + } + /> } diff --git a/src/bookmarks/api/bookmark.ts b/src/bookmarks/api/bookmark.ts index 84191dd9..8af97597 100644 --- a/src/bookmarks/api/bookmark.ts +++ b/src/bookmarks/api/bookmark.ts @@ -9,8 +9,6 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query'; -import { useNavigate } from 'react-router-dom'; -import { AxiosError } from 'axios'; import dayjs from 'dayjs'; import 'dayjs/locale/ko'; import { GET_LIKE_BOOKMARK_LIST } from './like'; @@ -790,63 +788,6 @@ export const usePUTBookmarkQuery = ({ }); }; -// 북마크 신고 -interface POSTBookmarkReportRequest { - reporterId: number; - reportedId: number; - content: string; -} - -const postBookmarkReportAPI = async (params: POSTBookmarkReportRequest) => { - const { data } = await client({ - method: 'post', - url: '/reports/bookmarks', - data: params, - }); - - return data; -}; - -export interface POSTBookmarkReportMutation { - reporterId: number; -} - -export const usePOSTBookmarkReportMutation = ({ - reporterId, -}: POSTBookmarkReportMutation) => { - const queryClient = useQueryClient(); - const toast = useToast(); - const router = useNavigate(); - return useMutation(postBookmarkReportAPI, { - onSuccess: () => { - queryClient.invalidateQueries( - GET_BOOKMARK_LIST(reporterId, '📖 전체', 0), - ); - queryClient.invalidateQueries( - GET_BOOKMARK_LIST(reporterId, '👀 읽음', 0), - ); - queryClient.invalidateQueries( - GET_BOOKMARK_LIST(reporterId, '🫣 읽지 않음', 0), - ); - toast.fireToast({ - message: '신고 되었습니다', - mode: 'SUCCESS', - }); - router(-1); - }, - onError: (e: AxiosError) => { - const errorCode = e.response?.status; - if (errorCode && errorCode === 409) { - toast.fireToast({ - message: '이미 신고한 북마크에요', - mode: 'DELETE', - }); - router(-1); - } - }, - }); -}; - // TODO : 추후 테스트 코드 작성 const getKeyofObject = (obj: T, value: unknown) => (Object.keys(obj) as (keyof T)[]).find((key) => obj[key] === value); diff --git a/src/comment/api/Comment.ts b/src/comment/api/Comment.ts index 2b10c6cd..29a5d553 100644 --- a/src/comment/api/Comment.ts +++ b/src/comment/api/Comment.ts @@ -7,8 +7,6 @@ import { refetchAllBookmarkQuery, } from '@/bookmarks/api/bookmark'; import useToast from '@/common-ui/Toast/hooks/useToast'; -import { useNavigate } from 'react-router-dom'; -import { AxiosError } from 'axios'; import useAuthStore from '@/store/auth'; import useBookmarkStore from '@/store/bookmark'; @@ -213,48 +211,3 @@ export const useDELETECommentQuery = ({ }, }); }; - -// 댓글 신고 -interface POSTCommentReportRequest { - reporterId: number; - reportedId: number; - content: string; -} - -const postCommentReportAPI = async (params: POSTCommentReportRequest) => { - const { data } = await client({ - method: 'post', - url: '/reports/comments', - data: params, - }); - - return data; -}; - -export interface POSTBookmarkReportMutation { - reporterId: number; -} - -export const usePOSTReportCommentQuery = () => { - const toast = useToast(); - const router = useNavigate(); - return useMutation(postCommentReportAPI, { - onSuccess: () => { - toast.fireToast({ - message: '신고 되었습니다', - mode: 'SUCCESS', - }); - router(-1); - }, - onError: (e: AxiosError) => { - const errorCode = e.response?.status; - if (errorCode && errorCode === 409) { - toast.fireToast({ - message: '이미 신고한 북마크에요', - mode: 'DELETE', - }); - router(-1); - } - }, - }); -}; diff --git a/src/common-ui/Error/ApiErrorBoundary.tsx b/src/common-ui/Error/ApiErrorBoundary.tsx index 44d815a9..c34edbfd 100644 --- a/src/common-ui/Error/ApiErrorBoundary.tsx +++ b/src/common-ui/Error/ApiErrorBoundary.tsx @@ -3,12 +3,17 @@ import { AxiosError } from 'axios'; import NetworkError from './NetworkError'; import { PostBridgeParams } from '@/common/service/hooks/useWebview'; -type ErrorType = 'NO_USER_INFO' | 'PRIVATE_BOOKMARK' | 'DUPLICATED_NICKNAME'; +type ErrorType = + | 'NO_USER_INFO' + | 'PRIVATE_BOOKMARK' + | 'DUPLICATED_NICKNAME' + | 'DUPLICATE_REPORT'; export const ErrorTypes: Record = { NO_USER_INFO: 'M001', DUPLICATED_NICKNAME: 'M002', PRIVATE_BOOKMARK: 'B002', + DUPLICATE_REPORT: 'R002', } as const; interface CustomData { diff --git a/src/constants/navigatePath.ts b/src/constants/navigatePath.ts index fd29b34a..d9653971 100644 --- a/src/constants/navigatePath.ts +++ b/src/constants/navigatePath.ts @@ -18,6 +18,7 @@ const navigatePath = { CATEGORY_LIST: '/category/list', COMMENT_REPORT: '/comment/:id/report', BOOKMARK_REPORT: '/bookmark/:id/report', + MEMBER_REPORT: '/member/:id/report', COMMENT: '/comment', LIKE_PAGE: '/likes', INTRODUCE: '/introduce', diff --git a/src/pages/FriendBookmarkPage.tsx b/src/pages/FriendBookmarkPage.tsx index 57387ffd..29d1c0e1 100644 --- a/src/pages/FriendBookmarkPage.tsx +++ b/src/pages/FriendBookmarkPage.tsx @@ -6,7 +6,7 @@ import BookmarkUserInfo from '@/bookmarks/ui/BookmarkUserInfo'; import useCategory from '@/bookmarks/service/hooks/home/useCategory'; import useReadList from '@/bookmarks/service/hooks/home/useReadList'; import getRem from '@/utils/getRem'; -import { useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import Header from '@/common-ui/Header/Header'; import TriggerBottomSheet from '@/common-ui/BottomSheet/TriggerBottomSheet'; import IconButton from '@/common/ui/IconButton'; @@ -25,6 +25,7 @@ import useBookmarkStore from '@/store/bookmark'; import SkeletonBookmarkUserInfo from '@/bookmarks/ui/SkeletonBookmarkUserInfo'; import PullToRefresh from '@/common-ui/PullToRefresh'; import useHandleRefresh from '@/common/service/hooks/useHandleRefresh'; +import { navigatePath } from '@/constants/navigatePath'; const FriendBookmarkPage = () => { // FIRST RENDER @@ -48,6 +49,12 @@ const FriendBookmarkPage = () => { // USER INTERACTION // 뒤로가기 + // 1. 상단 more > 신고하기 + const navigate = useNavigate(); + const onClick_신고하기 = () => { + navigate(navigatePath.MEMBER_REPORT.replace(':id', String(friendId))); + }; + // 1. 상단 more > 차단하기 const { mutate: postBlockMember } = usePOSTBlockMemberQuery({ memberId }); const onClick_차단하기 = () => { @@ -82,6 +89,9 @@ const FriendBookmarkPage = () => { as={ {}} name="more" size="s" />} /> + + 신고하기 + {!!profileInfo?.isBlocked && ( 차단해제 diff --git a/src/pages/ReportPage.tsx b/src/pages/ReportPage.tsx index a9f9b826..d3f27c77 100644 --- a/src/pages/ReportPage.tsx +++ b/src/pages/ReportPage.tsx @@ -1,10 +1,9 @@ -import { usePOSTBookmarkReportMutation } from '@/bookmarks/api/bookmark'; import BookmarkReportList from '@/bookmarks/ui/Report/BookmarkReportList'; import BookmarkReportWrite from '@/bookmarks/ui/Report/BookmarkReportWrite'; -import { usePOSTReportCommentQuery } from '@/comment/api/Comment'; import BottomFixedButton from '@/common-ui/BottomFixedButton'; import Header from '@/common-ui/Header/Header'; import Text from '@/common-ui/Text'; +import { REPORT_TYPE, usePostReportMutation } from '@/report/api/report'; import useAuthStore from '@/store/auth'; import getRem from '@/utils/getRem'; import styled from '@emotion/styled'; @@ -14,7 +13,7 @@ import { useParams } from 'react-router-dom'; export type ReportMode = 'CHECK' | 'WRITE'; interface ReportPageProps { - mode: 'BOOKMARK' | 'COMMENT'; + mode: REPORT_TYPE; } const ReportPage = ({ mode }: ReportPageProps) => { @@ -29,27 +28,20 @@ const ReportPage = ({ mode }: ReportPageProps) => { }; // TODO : 신고하기 버튼 클릭 시 신고 API 호출 - const { mutate: reportBookmark } = usePOSTBookmarkReportMutation({ + const { mutate: reportBookmark } = usePostReportMutation({ reporterId: memberId, + reportType: mode, }); - const { mutate: reportComment } = usePOSTReportCommentQuery(); + const onSubmit = (e: React.FormEvent) => { e.preventDefault(); const content = reportMode === 'WRITE' ? reportText : selectedReport; - if (mode === 'BOOKMARK') { - reportBookmark({ - reportedId: Number(id), - reporterId: memberId, - content, - }); - } - if (mode === 'COMMENT') { - reportComment({ - reportedId: Number(id), - reporterId: memberId, - content, - }); - } + reportBookmark({ + reportedId: Number(id), + reporterId: memberId, + content, + reportType: mode, + }); }; const buttonDisabled = diff --git a/src/report/api/report.ts b/src/report/api/report.ts new file mode 100644 index 00000000..686a7f72 --- /dev/null +++ b/src/report/api/report.ts @@ -0,0 +1,130 @@ +import { GET_BOOKMARK_LIST } from '@/bookmarks/api/bookmark'; +import { + CustomAxiosError, + ErrorTypes, +} from '@/common-ui/Error/ApiErrorBoundary'; +import useToast from '@/common-ui/Toast/hooks/useToast'; +import client from '@/common/service/client'; +import { + QueryClient, + useMutation, + useQueryClient, +} from '@tanstack/react-query'; +import { useNavigate } from 'react-router-dom'; + +export type REPORT_TYPE = 'BOOKMARK' | 'COMMENT' | 'MEMBER'; + +// 북마크 신고 +interface POSTBookmarkReportRequest { + reporterId: number; + reportedId: number; + content: string; + reportType: REPORT_TYPE; +} + +const postReportAPI = async (postData: POSTBookmarkReportRequest) => { + const { data } = await client({ + method: 'post', + url: '/reports', + data: postData, + }); + + return data; +}; + +export interface POSTBookmarkReportMutation { + reporterId: number; + reportType: REPORT_TYPE; +} + +export const usePostReportMutation = ({ + reporterId, + reportType, +}: POSTBookmarkReportMutation) => { + const queryClient = useQueryClient(); + const { fireToast } = useToast(); + const router = useNavigate(); + return useMutation( + postReportAPI, + refetchReportQuery(reporterId, reportType, queryClient, fireToast, router), + ); +}; + +const refetchReportQuery = ( + reporterId: number, + reportType: REPORT_TYPE, + queryClient: QueryClient, + fireToast: ReturnType['fireToast'], + router: ReturnType, +) => { + if (reportType === 'BOOKMARK') { + return { + onSuccess: () => { + // NOTE : 추후 어드민 페이지 개발 후 사용될 REFETCH 기능 + queryClient.invalidateQueries( + GET_BOOKMARK_LIST(reporterId, '📖 전체', 0), + ); + queryClient.invalidateQueries( + GET_BOOKMARK_LIST(reporterId, '👀 읽음', 0), + ); + queryClient.invalidateQueries( + GET_BOOKMARK_LIST(reporterId, '🫣 읽지 않음', 0), + ); + fireToast({ + message: '신고 되었습니다', + mode: 'SUCCESS', + }); + router(-1); + }, + onError: (e: CustomAxiosError) => { + if (e.response?.data.code === ErrorTypes.PRIVATE_BOOKMARK) { + fireToast({ + message: '이미 신고한 북마크에요', + mode: 'DELETE', + }); + } + router(-1); + }, + }; + } else if (reportType === 'COMMENT') { + return { + onSuccess: () => { + fireToast({ + message: '신고 되었습니다', + mode: 'SUCCESS', + }); + router(-1); + }, + onError: (e: CustomAxiosError) => { + if (e.response?.data.code === ErrorTypes.PRIVATE_BOOKMARK) { + fireToast({ + message: '이미 신고한 댓글이에요', + mode: 'DELETE', + }); + router(-1); + } + }, + }; + } else if (reportType === 'MEMBER') { + return { + onSuccess: () => { + fireToast({ + message: '신고 되었습니다', + mode: 'SUCCESS', + }); + router(-1); + }, + onError: (e: CustomAxiosError) => { + if (e.response?.data.code === ErrorTypes.PRIVATE_BOOKMARK) { + fireToast({ + message: '이미 신고한 북마크에요', + mode: 'DELETE', + }); + router(-1); + } + }, + }; + } else { + throw new Error('reportType이 잘못되었습니다'); + } +}; diff --git a/src/store/toast.ts b/src/store/toast.ts index 4e1f86e4..e2c383e1 100644 --- a/src/store/toast.ts +++ b/src/store/toast.ts @@ -9,6 +9,8 @@ export type ToastMessage = | '앗! 유효하지 않은 주소에요' | '신고 되었습니다' | '이미 신고한 북마크에요' + | '이미 신고한 유저에요' + | '이미 신고한 댓글이에요' | '앗! 알림 설정 기준일은 1일 이상이어야 해요' | '차단된 사용자는 팔로우 할 수 없어요' | '준비 중인 기능이에요'