From aa055a7c5d2b5e2da921a5480af2d00c014ccc56 Mon Sep 17 00:00:00 2001 From: gxxrxn Date: Tue, 21 May 2024 02:57:11 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=203d=20=EC=B1=85=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20placeholder=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - global css에 bg-blur 클래스 추가 --- src/styles/global.css | 5 ++++ src/v1/bookShelf/BookShelf.tsx | 54 ++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/styles/global.css b/src/styles/global.css index 87d0472b..5241fac0 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -75,4 +75,9 @@ .w-app { @apply relative -left-[2rem] w-[calc(100%+4rem)]; } + + .bg-blur { + box-shadow: inset 0 0 3rem #dddddd; + @apply bg-placeholder; + } } diff --git a/src/v1/bookShelf/BookShelf.tsx b/src/v1/bookShelf/BookShelf.tsx index f628108d..daa4d568 100644 --- a/src/v1/bookShelf/BookShelf.tsx +++ b/src/v1/bookShelf/BookShelf.tsx @@ -1,12 +1,17 @@ -import { APIBookshelf } from '@/types/bookshelf'; -import { IconArrowRight, IconHeart } from '@public/icons'; +'use client'; + +import { ReactNode, useState } from 'react'; import Link from 'next/link'; -import Badge from '@/v1/base/Badge'; import Image from 'next/image'; -import { APIBook } from '@/types/book'; -import { ReactNode, useState } from 'react'; + import ColorThief from 'colorthief'; +import { APIBook } from '@/types/book'; +import { APIBookshelf } from '@/types/bookshelf'; +import { IconArrowRight, IconHeart } from '@public/icons'; + +import Badge from '@/v1/base/Badge'; + const BookShelf = ({ children }: { children: ReactNode }) => { return <>{children}; }; @@ -58,8 +63,10 @@ const Books = ({ books }: BooksProps) => { const Book = ({ imageUrl, bookId, + title, }: Pick) => { const [bookSpineColor, setBookSpineColor] = useState(); + const placeholderClassName = bookSpineColor ? '' : 'bg-blur'; const handleOnLoadImage = (image: HTMLImageElement) => { const colorThief = new ColorThief(); @@ -71,44 +78,55 @@ const Book = ({ }; return ( -
+ {/** 책 옆면 (책등) */}
+ > + {/** 옆면과 표지 사이 여백을 메꾸기 위해 추가 */} +
+
+ + {/** 책 하단 그림자 */}
- book cover - -
+
+ ); }; From a84deb27e1b1e86079711dca6cad9a5ae39f2ab1 Mon Sep 17 00:00:00 2001 From: gxxrxn Date: Tue, 21 May 2024 21:16:13 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EC=B1=85=EC=9E=A5=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A0=95=EB=B3=B4=20query=20suspense=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 책장페이지 ssr 되도록 컴포넌트 분리 --- src/app/bookshelf/[bookshelfId]/page.tsx | 189 +++++++++++------- .../bookshelf/useBookShelfInfoQuery.ts | 23 ++- 2 files changed, 126 insertions(+), 86 deletions(-) diff --git a/src/app/bookshelf/[bookshelfId]/page.tsx b/src/app/bookshelf/[bookshelfId]/page.tsx index 0d4c693f..816d5e5a 100644 --- a/src/app/bookshelf/[bookshelfId]/page.tsx +++ b/src/app/bookshelf/[bookshelfId]/page.tsx @@ -1,23 +1,26 @@ 'use client'; import { useEffect } from 'react'; -import { useInView } from 'react-intersection-observer'; import Link from 'next/link'; +import { useInView } from 'react-intersection-observer'; + +import type { APIBookshelf, APIBookshelfInfo } from '@/types/bookshelf'; + import useBookShelfBooksQuery from '@/queries/bookshelf/useBookShelfBookListQuery'; import useBookShelfInfoQuery from '@/queries/bookshelf/useBookShelfInfoQuery'; import useMutateBookshelfLikeQuery from '@/queries/bookshelf/useMutateBookshelfLikeQuery'; -import useToast from '@/v1/base/Toast/useToast'; +import { useMyProfileId } from '@/queries/user/useMyProfileQuery'; import { checkAuthentication } from '@/utils/helpers'; import { IconKakao } from '@public/icons'; +import { KAKAO_LOGIN_URL } from '@/constants/url'; + +import useToast from '@/v1/base/Toast/useToast'; import TopNavigation from '@/v1/base/TopNavigation'; import BookShelfRow from '@/v1/bookShelf/BookShelfRow'; import Button from '@/v1/base/Button'; import LikeButton from '@/v1/base/LikeButton'; import BackButton from '@/v1/base/BackButton'; import ShareButton from '@/v1/base/ShareButton'; -import type { APIBookshelf, APIBookshelfInfo } from '@/types/bookshelf'; - -const KAKAO_OAUTH_LOGIN_URL = `${process.env.NEXT_PUBLIC_API_URL}/oauth2/authorize/kakao?redirect_uri=${process.env.NEXT_PUBLIC_CLIENT_REDIRECT_URI}`; export default function UserBookShelfPage({ params: { bookshelfId }, @@ -26,13 +29,34 @@ export default function UserBookShelfPage({ bookshelfId: APIBookshelf['bookshelfId']; }; }) { + return ( +
+ + + + + + + + + + + +
+ ); +} + +const BookShelfInfo = ({ bookshelfId }: { bookshelfId: number }) => { const isAuthenticated = checkAuthentication(); - const { data, isSuccess } = useBookShelfInfoQuery({ bookshelfId }); + const { show: showToast } = useToast(); + + const { data } = useBookShelfInfoQuery(bookshelfId); + const { isLiked, likeCount, userId, userNickname, job } = data; + const { mutate: mutateBookshelfLike } = useMutateBookshelfLikeQuery(bookshelfId); - const { show: showToast } = useToast(); - if (!isSuccess) return null; + const { data: myId } = useMyProfileId({ enabled: isAuthenticated }); const handleClickLikeButton = () => { if (!isAuthenticated) { @@ -40,50 +64,42 @@ export default function UserBookShelfPage({ return; } - mutateBookshelfLike(data.isLiked); + if (userId === myId) { + showToast({ + message: '내 책장에는 좋아요를 누를 수 없어요.', + type: 'normal', + }); + return; + } + + mutateBookshelfLike(isLiked); }; return ( -
- - - - - - - - -
-

- {data.userNickname} - 님의 책장 -

-
- - {`${data.job.jobGroupKoreanName} • ${data.job.jobNameKoreanName}`} - - -
+
+

+ {userNickname} + 님의 책장 +

+
+ + {`${job.jobGroupKoreanName} • ${job.jobNameKoreanName}`} + +
- -
); -} +}; const BookShelfContent = ({ bookshelfId, - userNickname, }: { bookshelfId: APIBookshelf['bookshelfId']; - userNickname: APIBookshelfInfo['userNickname']; + userNickname?: APIBookshelfInfo['userNickname']; }) => { const isAuthenticated = checkAuthentication(); const { ref, inView } = useInView(); @@ -93,7 +109,6 @@ const BookShelfContent = ({ fetchNextPage, hasNextPage, isSuccess, - isFetching, isFetchingNextPage, } = useBookShelfBooksQuery({ bookshelfId }); @@ -106,47 +121,67 @@ const BookShelfContent = ({ // TODO: Suspense 적용 if (!isSuccess) return null; - return isAuthenticated ? ( + return ( <> - {booksData.pages.map(page => - page.books.map((rowBooks, idx) => ( - - )) + {isAuthenticated ? ( + <> + {booksData.pages.map(page => + page.books.map((rowBooks, idx) => ( + + )) + )} + {!isFetchingNextPage &&
} + + ) : ( + <> + + + + )} - - {isFetching && !isFetchingNextPage ? null :
} - - ) : ( - <> - -
- -
-
-

- 지금 로그인하면 -
- 책장에 담긴 모든 책을 볼 수 있어요! -

-

- {userNickname}님의 책장에서 - 다양한 -
- 인사이트를 얻을 수 있어요. -

- - - -
); }; +const DummyBookShelfRow = () => ( +
+ +
+); + +const BookShelfLoginBox = ({ + bookshelfId, +}: { + bookshelfId: APIBookshelf['bookshelfId']; +}) => { + const { data } = useBookShelfInfoQuery(bookshelfId); + const { userNickname } = data; + + return ( +
+

+ 지금 로그인하면 +
+ 책장에 담긴 모든 책을 볼 수 있어요! +

+

+ {userNickname}님의 책장에서 + 다양한 +
+ 인사이트를 얻을 수 있어요. +

+ + + +
+ ); +}; + const initialBookImageUrl = [ { bookId: 1, title: 'book1', imageUrl: '/images/book-cover/book1.jpeg' }, { bookId: 2, title: 'book2', imageUrl: '/images/book-cover/book2.jpeg' }, diff --git a/src/queries/bookshelf/useBookShelfInfoQuery.ts b/src/queries/bookshelf/useBookShelfInfoQuery.ts index 80b151b6..c6230a80 100644 --- a/src/queries/bookshelf/useBookShelfInfoQuery.ts +++ b/src/queries/bookshelf/useBookShelfInfoQuery.ts @@ -1,15 +1,20 @@ -import bookshelfAPI from '@/apis/bookshelf'; +import { UseQueryOptions } from '@tanstack/react-query'; import { APIBookshelfInfo } from '@/types/bookshelf'; -import { useQuery } from '@tanstack/react-query'; +import useQueryWithSuspense from '@/hooks/useQueryWithSuspense'; +import bookshelfAPI from '@/apis/bookshelf'; import bookShelfKeys from './key'; -const useBookShelfInfoQuery = ({ - bookshelfId, -}: { - bookshelfId: APIBookshelfInfo['bookshelfId']; -}) => - useQuery(bookShelfKeys.info(bookshelfId), () => - bookshelfAPI.getBookshelfInfo(bookshelfId).then(response => response.data) +const useBookShelfInfoQuery = ( + bookshelfId: APIBookshelfInfo['bookshelfId'], + options?: UseQueryOptions +) => + useQueryWithSuspense( + bookShelfKeys.info(bookshelfId), + () => + bookshelfAPI + .getBookshelfInfo(bookshelfId) + .then(response => response.data), + options ); export default useBookShelfInfoQuery; From 831b6ca2128e052157ff19823ffde1f67832e2bc Mon Sep 17 00:00:00 2001 From: gxxrxn Date: Tue, 21 May 2024 21:52:37 +0900 Subject: [PATCH 3/5] =?UTF-8?q?style:=20=EC=B1=85=EC=9E=A5=20padding=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v1/bookShelf/BookShelf.tsx | 4 ++-- src/v1/bookShelf/BookShelfCard.tsx | 2 +- src/v1/bookShelf/BookShelfRow.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v1/bookShelf/BookShelf.tsx b/src/v1/bookShelf/BookShelf.tsx index daa4d568..4a9539ac 100644 --- a/src/v1/bookShelf/BookShelf.tsx +++ b/src/v1/bookShelf/BookShelf.tsx @@ -29,7 +29,7 @@ type InfoProps = Omit; const Info = ({ bookshelfName, bookshelfId, likeCount }: InfoProps) => { return ( -
+
{bookshelfName}
@@ -50,7 +50,7 @@ type BooksProps = Pick; const Books = ({ books }: BooksProps) => { return ( -
    +
      {books.map(book => (
    • diff --git a/src/v1/bookShelf/BookShelfCard.tsx b/src/v1/bookShelf/BookShelfCard.tsx index a60d197e..88f58efa 100644 --- a/src/v1/bookShelf/BookShelfCard.tsx +++ b/src/v1/bookShelf/BookShelfCard.tsx @@ -11,7 +11,7 @@ const BookShelfCard = ({
      -
      +
      ) => { return ( -
      +
      From 534785b95695edbcf244e2241ae49066c960eea7 Mon Sep 17 00:00:00 2001 From: gxxrxn Date: Tue, 21 May 2024 22:21:17 +0900 Subject: [PATCH 4/5] =?UTF-8?q?style:=20BookShelf.Book=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=8C=A8=EB=94=A9=201=20->=201.5?= =?UTF-8?q?rem=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v1/bookShelf/BookShelf.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v1/bookShelf/BookShelf.tsx b/src/v1/bookShelf/BookShelf.tsx index 4a9539ac..1f1bc0ff 100644 --- a/src/v1/bookShelf/BookShelf.tsx +++ b/src/v1/bookShelf/BookShelf.tsx @@ -50,7 +50,7 @@ type BooksProps = Pick; const Books = ({ books }: BooksProps) => { return ( -
        +
          {books.map(book => (
        • From 63a8976811ae00d13e2986472a65802aa37e48e7 Mon Sep 17 00:00:00 2001 From: gxxrxn Date: Wed, 22 May 2024 15:30:50 +0900 Subject: [PATCH 5/5] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20prop,=20=ED=83=9C=EA=B7=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookshelf/[bookshelfId]/page.tsx | 32 ++++++++++-------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/app/bookshelf/[bookshelfId]/page.tsx b/src/app/bookshelf/[bookshelfId]/page.tsx index 816d5e5a..fbd85b00 100644 --- a/src/app/bookshelf/[bookshelfId]/page.tsx +++ b/src/app/bookshelf/[bookshelfId]/page.tsx @@ -4,7 +4,7 @@ import { useEffect } from 'react'; import Link from 'next/link'; import { useInView } from 'react-intersection-observer'; -import type { APIBookshelf, APIBookshelfInfo } from '@/types/bookshelf'; +import type { APIBookshelf } from '@/types/bookshelf'; import useBookShelfBooksQuery from '@/queries/bookshelf/useBookShelfBookListQuery'; import useBookShelfInfoQuery from '@/queries/bookshelf/useBookShelfInfoQuery'; @@ -99,7 +99,6 @@ const BookShelfContent = ({ bookshelfId, }: { bookshelfId: APIBookshelf['bookshelfId']; - userNickname?: APIBookshelfInfo['userNickname']; }) => { const isAuthenticated = checkAuthentication(); const { ref, inView } = useInView(); @@ -121,28 +120,23 @@ const BookShelfContent = ({ // TODO: Suspense 적용 if (!isSuccess) return null; - return ( + return isAuthenticated ? ( <> - {isAuthenticated ? ( - <> - {booksData.pages.map(page => - page.books.map((rowBooks, idx) => ( - - )) - )} - {!isFetchingNextPage &&
          } - - ) : ( - <> - - - - + {booksData.pages.map(page => + page.books.map((rowBooks, idx) => ( + + )) )} + {!isFetchingNextPage &&
          } + + ) : ( + <> + + + ); }; - const DummyBookShelfRow = () => (