Skip to content

Commit 6d87943

Browse files
authored
[#497] [책 상세] 책 상세 페이지 api 연결 (#500)
* feat: bookmark 기능 구현 - api, query 함수명에 명확히 bookmark를 포함하도록 수정 * feat: 책 코멘트 작성 기능 구현 * feat: 코멘트 수정 기능 구현 * refactor: 코멘트 수정 mutation 파라미터 구조 수정 * feat: 코멘트 삭제 기능 구현 * fix: 코드리뷰 반영
1 parent bc9fafb commit 6d87943

18 files changed

+359
-95
lines changed

src/apis/book/index.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
APIBookCommentPagination,
55
APIBookDetail,
66
APIBookmarkedUserList,
7+
APICreateBookCommentRequest,
78
APIPatchBookCommentRequest,
89
APISearchedBook,
910
APISearchedBookPagination,
@@ -37,17 +38,17 @@ const bookAPI = {
3738
getBookInfo: (bookId: APIBook['bookId']) =>
3839
publicApi.get<APIBookDetail>(`/service-api/books/${bookId}`),
3940

40-
getBookUserInfo: (bookId: APIBook['bookId']) =>
41+
getBookmarkUserInfo: (bookId: APIBook['bookId']) =>
4142
publicApi.get<APIBookmarkedUserList>(`/service-api/books/${bookId}/users`),
4243

4344
createBook: ({ book }: { book: APISearchedBook }) =>
4445
publicApi.post<Pick<APIBook, 'bookId'>>('/service-api/books', book),
4546

4647
creaetComment: (
4748
bookId: APIBook['bookId'],
48-
{ comment }: { comment: APIPatchBookCommentRequest['comment'] }
49+
comment: APICreateBookCommentRequest['comment']
4950
) =>
50-
publicApi.post<APIPatchBookCommentRequest['comment']>(
51+
publicApi.post<APICreateBookCommentRequest['comment']>(
5152
`/service-api/books/${bookId}/comments`,
5253
{ comment }
5354
),
@@ -80,14 +81,14 @@ const bookAPI = {
8081
commentId: APIBookComment['commentId']
8182
) => publicApi.delete(`/service-api/books/${bookId}/comments/${commentId}`),
8283

83-
setBookMarked: (bookId: APIBook['bookId']) =>
84+
addBookmark: (bookId: APIBook['bookId']) =>
8485
bookshelfAPI.getMySummaryBookshelf().then(({ data: { bookshelfId } }) =>
8586
publicApi.post(`/service-api/bookshelves/${bookshelfId}/books`, {
8687
bookId,
8788
})
8889
),
8990

90-
unsetBookMarked: (bookId: APIBook['bookId']) =>
91+
removeBookmark: (bookId: APIBook['bookId']) =>
9192
bookshelfAPI
9293
.getMySummaryBookshelf()
9394
.then(({ data: { bookshelfId } }) =>

src/app/book/[bookId]/page.tsx

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
'use client';
2+
import { useRef } from 'react';
23

34
import { APIBook } from '@/types/book';
45
import { useBookTitle } from '@/queries/book/useBookInfoQuery';
6+
import { useHasBookComment } from '@/queries/book/useBookCommentsQuery';
7+
import useCreateBookCommentMutation from '@/queries/book/useCreateBookCommentMutation';
8+
import useToast from '@/v1/base/Toast/useToast';
9+
import useDisclosure from '@/hooks/useDisclosure';
10+
import {
11+
checkAuthentication,
12+
isAxiosErrorWithCustomCode,
13+
} from '@/utils/helpers';
14+
import { SERVICE_ERROR_MESSAGE } from '@/constants';
515

616
import Skeleton from '@/v1/base/Skeleton';
717
import SSRSafeSuspense from '@/components/SSRSafeSuspense';
818
import TopNavigation from '@/v1/base/TopNavigation';
919
import BottomActionButton from '@/v1/base/BottomActionButton';
20+
import LoginBottomActionButton from '@/v1/base/LoginBottomActionButton';
21+
import CommentDrawer from '@/v1/comment/CommentDrawer';
1022
import BackButton from '@/v1/base/BackButton';
1123
import BookInfo, { BookInfoSkeleton } from '@/v1/book/detail/BookInfo';
1224
import BookCommentList from '@/v1/comment/BookCommentList';
@@ -16,6 +28,8 @@ const BookDetailPage = ({
1628
}: {
1729
params: { bookId: APIBook['bookId'] };
1830
}) => {
31+
const isAuthenticated = checkAuthentication();
32+
1933
return (
2034
<>
2135
<BookTopNavigation bookId={bookId} />
@@ -27,8 +41,12 @@ const BookDetailPage = ({
2741
<BookCommentList bookId={bookId} />
2842
</div>
2943
</div>
44+
{isAuthenticated ? (
45+
<AddBookCommentButton bookId={bookId} />
46+
) : (
47+
<LoginBottomActionButton />
48+
)}
3049
</SSRSafeSuspense>
31-
<BottomActionButton>코멘트 작성하기</BottomActionButton>
3250
</>
3351
);
3452
};
@@ -63,6 +81,65 @@ const Heading = ({ text }: { text: string }) => (
6381
<p className="text-xl font-bold">{text}</p>
6482
);
6583

84+
const AddBookCommentButton = ({ bookId }: { bookId: APIBook['bookId'] }) => {
85+
const {
86+
isOpen: isDrawerOpen,
87+
onOpen: onDrawerOpen,
88+
onClose: onDrawerClose,
89+
} = useDisclosure();
90+
const { show: showToast } = useToast();
91+
92+
const commentRef = useRef<HTMLTextAreaElement>(null);
93+
const createComment = useCreateBookCommentMutation(bookId);
94+
95+
const { data: hasBookComment } = useHasBookComment(bookId);
96+
97+
const handleCommentCreate = () => {
98+
const comment = commentRef.current?.value;
99+
100+
if (!comment) {
101+
return;
102+
}
103+
104+
createComment.mutate(comment, {
105+
onSuccess: () => {
106+
onDrawerClose();
107+
showToast({ type: 'success', message: '코멘트를 등록했어요 🎉' });
108+
},
109+
onError: error => {
110+
if (isAxiosErrorWithCustomCode(error)) {
111+
const { code } = error.response.data;
112+
const message = SERVICE_ERROR_MESSAGE[code];
113+
showToast({ type: 'error', message });
114+
return;
115+
}
116+
117+
showToast({ type: 'error', message: '코멘트를 등록하지 못했어요 🥲' });
118+
},
119+
});
120+
};
121+
122+
if (hasBookComment) {
123+
return null;
124+
}
125+
126+
return (
127+
<>
128+
<BottomActionButton onClick={onDrawerOpen}>
129+
코멘트 작성하기
130+
</BottomActionButton>
131+
<CommentDrawer
132+
isOpen={isDrawerOpen}
133+
onClose={onDrawerClose}
134+
title="책 코멘트 작성하기"
135+
placeholder="작성해주신 코멘트가 다른 사람들에게 많은 도움이 될 거예요!"
136+
onConfirm={handleCommentCreate}
137+
ref={commentRef}
138+
/>
139+
</>
140+
);
141+
};
142+
66143
const BookTitleSkeleton = () => (
67144
<Skeleton>
68145
<Skeleton.Text fontSize="medium" width="20rem" />

src/app/group/[groupId]/page.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
'use client';
22

3-
import Link from 'next/link';
4-
53
import { checkAuthentication } from '@/utils/helpers';
6-
import { KAKAO_LOGIN_URL } from '@/constants/url';
74

85
import SSRSafeSuspense from '@/components/SSRSafeSuspense';
96
import BookGroupInfo from '@/v1/bookGroup/detail/BookGroupInfo';
107
import BookGroupCommentList from '@/v1/comment/BookGroupCommentList';
118
import BookGroupNavigation from '@/v1/bookGroup/BookGroupNavigation';
129
import JoinBookGroupButton from '@/v1/bookGroup/detail/JoinBookGroupButton';
13-
import BottomActionButton from '@/v1/base/BottomActionButton';
10+
import LoginBottomActionButton from '@/v1/base/LoginBottomActionButton';
1411

1512
const DetailBookGroupPage = ({
1613
params: { groupId },
@@ -65,9 +62,3 @@ const PageSkeleton = () => (
6562
);
6663

6764
const Divider = () => <p className="w-app h-[0.5rem] bg-background"></p>;
68-
69-
const LoginBottomActionButton = () => (
70-
<Link href={KAKAO_LOGIN_URL}>
71-
<BottomActionButton>로그인 및 회원가입</BottomActionButton>
72-
</Link>
73-
);

src/components/ReactQueryProvider.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const ReactQueryProvider: NextPage<PropTypes> = ({ children }) => {
1313
new QueryClient({
1414
defaultOptions: {
1515
queries: {
16-
refetchOnWindowFocus: true,
1716
retry: 0,
1817
},
1918
},

src/queries/book/useBookCommentPatchMutation.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/queries/book/useBookCommentsQuery.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,10 @@ export const useBookComments = (bookId: APIBook['bookId']) =>
4747
useBookCommentsQuery(bookId, {
4848
select: transformBookCommentsData,
4949
});
50+
51+
export const useHasBookComment = (bookId: APIBook['bookId']) =>
52+
useBookCommentsQuery(bookId, {
53+
select: ({ bookComments }) =>
54+
bookComments.filter(comment => comment.writtenByCurrentUser === true)
55+
.length,
56+
});

src/queries/book/useBookUserInfoQuery.ts renamed to src/queries/book/useBookmarkUserQuery.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import bookAPI from '@/apis/book';
44
import useQueryWithSuspense from '@/hooks/useQueryWithSuspense';
55
import bookKeys from './key';
66

7-
const useBookUserInfoQuery = <TData = APIBookmarkedUserList>(
7+
const useBookmarkUserQuery = <TData = APIBookmarkedUserList>(
88
bookId: number,
99
options?: UseQueryOptions<APIBookmarkedUserList, unknown, TData>
1010
) =>
1111
useQueryWithSuspense(
1212
bookKeys.bookmark(bookId),
13-
() => bookAPI.getBookUserInfo(bookId).then(({ data }) => data),
13+
() => bookAPI.getBookmarkUserInfo(bookId).then(({ data }) => data),
1414
options
1515
);
1616

17-
export default useBookUserInfoQuery;
17+
export default useBookmarkUserQuery;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query';
2+
3+
import { APIBook } from '@/types/book';
4+
import bookAPI from '@/apis/book';
5+
import bookKeys from './key';
6+
7+
const useCreateBookCommentMutation = (bookId: APIBook['bookId']) => {
8+
const queryClient = useQueryClient();
9+
10+
return useMutation({
11+
mutationFn: (newComment: string) =>
12+
bookAPI.creaetComment(bookId, newComment).then(({ data }) => data),
13+
onSuccess: () => {
14+
queryClient.invalidateQueries({ queryKey: bookKeys.comments(bookId) });
15+
},
16+
});
17+
};
18+
19+
export default useCreateBookCommentMutation;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query';
2+
3+
import { APIBook, APIBookComment } from '@/types/book';
4+
import bookAPI from '@/apis/book';
5+
import bookKeys from './key';
6+
7+
const useDeleteBookCommentMutation = (bookId: APIBook['bookId']) => {
8+
const queryClient = useQueryClient();
9+
10+
return useMutation({
11+
mutationFn: (commentId: APIBookComment['commentId']) =>
12+
bookAPI.deletComment(bookId, commentId).then(({ data }) => data),
13+
onSuccess: () => {
14+
queryClient.invalidateQueries({ queryKey: bookKeys.comments(bookId) });
15+
},
16+
});
17+
};
18+
19+
export default useDeleteBookCommentMutation;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query';
2+
3+
import { APIBook, APIBookComment } from '@/types/book';
4+
import bookAPI from '@/apis/book';
5+
import bookKeys from './key';
6+
7+
const usePatchBookCommentMutation = (bookId: APIBook['bookId']) => {
8+
const queryClient = useQueryClient();
9+
10+
return useMutation({
11+
mutationFn: (data: {
12+
commentId: APIBookComment['commentId'];
13+
comment: string;
14+
}) => bookAPI.patchComment({ bookId, data }).then(({ data }) => data),
15+
onSettled: () => {
16+
queryClient.invalidateQueries({ queryKey: bookKeys.comments(bookId) });
17+
},
18+
});
19+
};
20+
21+
export default usePatchBookCommentMutation;

0 commit comments

Comments
 (0)