diff --git a/components/features/my-page/recommend/RecommendedBookCard.tsx b/components/features/my-page/recommend/RecommendedBookCard.tsx index db5ccd3..6eee385 100644 --- a/components/features/my-page/recommend/RecommendedBookCard.tsx +++ b/components/features/my-page/recommend/RecommendedBookCard.tsx @@ -3,17 +3,18 @@ import { BookOpen } from "lucide-react"; import type { MyPageRecommendBook } from "@/types/myPage"; export function RecommendedBookCard({ book }: { book: MyPageRecommendBook }) { - const { title, authors, cover, url, publisher, publishedAt } = book; - const year = publishedAt ? publishedAt.slice(0, 4) : ""; + const { title, authors, thumbnail, url, publisher, price, salePrice } = book; + const hasDiscount = salePrice !== -1; + const hasThumbnail = thumbnail && thumbnail.trim() !== ""; return (
- {cover ? ( + {hasThumbnail ? ( {title} @@ -31,11 +32,21 @@ export function RecommendedBookCard({ book }: { book: MyPageRecommendBook }) { {authors.join(", ")} - - {publisher} - · - {year} - + {publisher} + {hasDiscount ? ( +
+ + {price.toLocaleString()}원 + + + {salePrice.toLocaleString()}원 + +
+ ) : ( + + {price.toLocaleString()}원 + + )}
diff --git a/components/features/my-page/recommend/RecommendedBookList.tsx b/components/features/my-page/recommend/RecommendedBookList.tsx index 7751103..048db40 100644 --- a/components/features/my-page/recommend/RecommendedBookList.tsx +++ b/components/features/my-page/recommend/RecommendedBookList.tsx @@ -1,37 +1,22 @@ "use client"; -import { useEffect, useState, useMemo } from "react"; +import { useEffect, useState } from "react"; import { Skeleton } from "@/components/ui/skeleton"; import { RecommendedBookListItem } from "./RecommendedBookListItem"; -import { MyPagePagination } from "../MyPagePagination"; import { fetchRecommendBooks } from "@/lib/mock/my-page-recommend-book"; -import type { MyPageRecommendBook } from "@/types/myPage"; - -const PAGE_SIZE = 10; +import type { MyPageRecommendBooksResponse } from "@/types/myPage"; function ListItemSkeleton() { return (
- {/* 썸네일 */} -
- {/* title */} - - {/* description */} - - {/* authors */} - - {/* bottom 영역 */}
- {/* publisher · year */} - - {/* price */}
@@ -40,24 +25,18 @@ function ListItemSkeleton() { } export function RecommendedBookList() { - const [books, setBooks] = useState([]); + const [booksData, setBooksData] = + useState(null); const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(false); - const [currentPage, setCurrentPage] = useState(1); useEffect(() => { fetchRecommendBooks() - .then((data) => setBooks(data)) + .then((data) => setBooksData(data)) .catch(() => setIsError(true)) .finally(() => setIsLoading(false)); }, []); - const totalPages = Math.ceil(books.length / PAGE_SIZE); - const pagedItems = useMemo( - () => books.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE), - [books, currentPage], - ); - if (isError) { return (

@@ -76,6 +55,16 @@ export function RecommendedBookList() { ); } + if (!booksData?.isPersonalized) { + return ( +

+ {booksData?.message ?? "아직 추천할 도서가 부족해요. 더 많은 글을 읽어보세요!"} +

+ ); + } + + const books = booksData.books; + if (books.length === 0) { return (

@@ -85,18 +74,10 @@ export function RecommendedBookList() { } return ( - <> -

- {pagedItems.map((book) => ( - - ))} -
- - +
+ {books.map((book) => ( + + ))} +
); } diff --git a/components/features/my-page/recommend/RecommendedBookListItem.tsx b/components/features/my-page/recommend/RecommendedBookListItem.tsx index 53507db..031e76a 100644 --- a/components/features/my-page/recommend/RecommendedBookListItem.tsx +++ b/components/features/my-page/recommend/RecommendedBookListItem.tsx @@ -7,17 +7,9 @@ export function RecommendedBookListItem({ }: { book: MyPageRecommendBook; }) { - const { - title, - authors, - description, - cover, - url, - price, - publisher, - publishedAt, - } = book; - const year = publishedAt ? publishedAt.slice(0, 4) : ""; + const { title, authors, contents, thumbnail, url, price, salePrice, publisher } = book; + const hasDiscount = salePrice !== -1; + const hasThumbnail = thumbnail && thumbnail.trim() !== ""; return (
- {cover ? ( - {title} + {hasThumbnail ? ( + {title} ) : (
@@ -40,23 +32,29 @@ export function RecommendedBookListItem({

{title}

- - {description && ( -

- {description} + {contents && ( +

+ {contents}

)}

{authors.join(", ")}

- - {publisher} - · - {year} - - {price && ( - - {price.toLocaleString()}원 - - )} + {publisher} +
+ {hasDiscount ? ( +
+ + {price.toLocaleString()}원 + + + {salePrice.toLocaleString()}원 + +
+ ) : ( + + {price.toLocaleString()}원 + + )} +
); diff --git a/components/features/my-page/recommend/RecommendedSection.tsx b/components/features/my-page/recommend/RecommendedSection.tsx index a6e6e10..1774573 100644 --- a/components/features/my-page/recommend/RecommendedSection.tsx +++ b/components/features/my-page/recommend/RecommendedSection.tsx @@ -13,7 +13,7 @@ import { fetchRecommendBooks } from "@/lib/mock/my-page-recommend-book"; import type { MyPageRecommendContentsResponse, MyPageRecommendYoutubeResponse, - MyPageRecommendBook, + MyPageRecommendBooksResponse, } from "@/types/myPage"; function SubSectionHeader({ title, href }: { title: string; href: string }) { @@ -62,7 +62,8 @@ export function RecommendedSection() { useState(null); const [videosData, setVideosData] = useState(null); - const [books, setBooks] = useState([]); + const [booksData, setBooksData] = + useState(null); const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(false); @@ -75,7 +76,7 @@ export function RecommendedSection() { .then(([postsData, vidsData, bks]) => { setHomePostsData(postsData); setVideosData(vidsData); - setBooks(bks); + setBooksData(bks); }) .catch(() => setIsError(true)) .finally(() => setIsLoading(false)); @@ -83,6 +84,7 @@ export function RecommendedSection() { const homePosts = homePostsData?.contents ?? []; const videos = videosData?.videos ?? []; + const books = booksData?.books ?? []; if (isError) { return ( @@ -164,6 +166,10 @@ export function RecommendedSection() { ))}
+ ) : !booksData?.isPersonalized ? ( +

+ {booksData?.message ?? "아직 추천할 도서가 부족해요. 더 많은 글을 읽어보세요!"} +

) : books.length === 0 ? (

추천 서적이 없습니다. @@ -171,7 +177,7 @@ export function RecommendedSection() { ) : (

{books.map((book) => ( - + ))}
)} diff --git a/lib/mock/my-page-recommend-book.ts b/lib/mock/my-page-recommend-book.ts index cb07514..7a08c12 100644 --- a/lib/mock/my-page-recommend-book.ts +++ b/lib/mock/my-page-recommend-book.ts @@ -1,156 +1,132 @@ -import type { MyPageRecommendBook } from "@/types/myPage"; +import type { + MyPageRecommendBook, + MyPageRecommendBooksResponse, +} from "@/types/myPage"; export const MOCK_RECOMMEND_BOOKS: MyPageRecommendBook[] = [ { - bookId: "book-001", title: "클린 아키텍처", authors: ["로버트 C. 마틴"], - description: - "소프트웨어 구조 설계의 핵심 원칙과 클린 아키텍처의 개념을 설명하는 책.", - cover: "https://picsum.photos/seed/book1/300/400", + publisher: "인사이트", + thumbnail: "https://picsum.photos/seed/book1/300/400", url: "https://www.yes24.com/Product/Goods/77283734", + contents: + "소프트웨어 구조 설계의 핵심 원칙과 클린 아키텍처의 개념을 설명하는 책.", price: 16000, - publisher: "인사이트", - publishedAt: "2019-08-20", + salePrice: 14400, }, { - bookId: "book-002", title: "가상 면접 사례로 배우는 대규모 시스템 설계 기초", authors: ["알렉스 쉬"], - description: - "대규모 시스템 설계 면접을 대비하기 위한 핵심 개념과 설계 방법을 다룬다.", - cover: "https://picsum.photos/seed/book2/300/400", + publisher: "인사이트", + thumbnail: "https://picsum.photos/seed/book2/300/400", url: "https://www.yes24.com/Product/Goods/102819435", + contents: + "대규모 시스템 설계 면접을 대비하기 위한 핵심 개념과 설계 방법을 다룬다.", price: 21000, - publisher: "인사이트", - publishedAt: "2021-11-24", + salePrice: 18900, }, { - bookId: "book-003", title: "이펙티브 타입스크립트", authors: ["댄 밴더캄"], - description: - "타입스크립트를 더 안전하고 효과적으로 사용하는 방법을 소개하는 실전 가이드.", - cover: null, + publisher: "인사이트", + thumbnail: null, url: "https://www.yes24.com/Product/Goods/102124327", + contents: + "타입스크립트를 더 안전하고 효과적으로 사용하는 방법을 소개하는 실전 가이드.", price: 32000, - publisher: "인사이트", - publishedAt: "2021-11-12", + salePrice: -1, }, { - bookId: "book-004", title: "데이터 중심 애플리케이션 설계", authors: ["마틴 클레퍼만"], - description: - "데이터 시스템의 설계 원칙과 분산 시스템의 핵심 개념을 깊이 있게 설명한다.", - cover: "https://picsum.photos/seed/book4/300/400", + publisher: "위키북스", + thumbnail: "https://picsum.photos/seed/book4/300/400", url: "https://www.yes24.com/Product/Goods/59566585", + contents: + "데이터 시스템의 설계 원칙과 분산 시스템의 핵심 개념을 깊이 있게 설명한다.", price: 28000, - publisher: "위키북스", - publishedAt: "2018-12-04", + salePrice: 25200, }, { - bookId: "book-005", title: "리팩터링 2판", authors: ["마틴 파울러"], - description: - "코드를 더 깔끔하게 만드는 리팩터링 기법을 JavaScript 예제와 함께 설명한다.", - cover: "https://picsum.photos/seed/book5/300/400", + publisher: "한빛미디어", + thumbnail: "https://picsum.photos/seed/book5/300/400", url: "https://www.yes24.com/Product/Goods/89649360", + contents: + "코드를 더 깔끔하게 만드는 리팩터링 기법을 JavaScript 예제와 함께 설명한다.", price: 38000, - publisher: "한빛미디어", - publishedAt: "2020-04-01", + salePrice: -1, }, { - bookId: "book-006", title: "HTTP 완벽 가이드", authors: ["데이빗 고울리", "브라이언 토티"], - description: - "웹의 근간이 되는 HTTP 프로토콜의 동작 원리와 최신 스펙을 상세히 다룬다.", - cover: null, + publisher: "인사이트", + thumbnail: null, url: "https://www.yes24.com/Product/Goods/15381085", + contents: + "웹의 근간이 되는 HTTP 프로토콜의 동작 원리와 최신 스펙을 상세히 다룬다.", price: 49000, - publisher: "인사이트", - publishedAt: "2014-12-15", + salePrice: 44100, }, { - bookId: "book-007", title: "자바스크립트 딥 다이브", authors: ["이웅모"], - description: - "자바스크립트의 동작 원리를 기초부터 깊이 있게 이해할 수 있는 국내 저술서.", - cover: "https://picsum.photos/seed/book7/300/400", + publisher: "위키북스", + thumbnail: "https://picsum.photos/seed/book7/300/400", url: "https://www.yes24.com/Product/Goods/92742567", + contents: + "자바스크립트의 동작 원리를 기초부터 깊이 있게 이해할 수 있는 국내 저술서.", price: 45000, - publisher: "위키북스", - publishedAt: "2020-09-25", + salePrice: -1, }, { - bookId: "book-008", title: "쏙쏙 들어오는 함수형 코딩", authors: ["에릭 노먼드"], - description: - "함수형 프로그래밍 패러다임을 실용적인 관점에서 쉽게 설명하는 입문서.", - cover: "https://picsum.photos/seed/book8/300/400", + publisher: "제이펍", + thumbnail: "https://picsum.photos/seed/book8/300/400", url: "https://www.yes24.com/Product/Goods/108748841", + contents: + "함수형 프로그래밍 패러다임을 실용적인 관점에서 쉽게 설명하는 입문서.", price: 33000, - publisher: "제이펍", - publishedAt: "2022-11-30", + salePrice: 29700, }, { - bookId: "book-009", title: "실용주의 프로그래머", authors: ["데이비드 토머스", "앤드류 헌트"], - description: - "더 나은 개발자가 되기 위한 실질적인 조언을 담은 개발 철학서의 고전.", - cover: null, + publisher: "인사이트", + thumbnail: null, url: "https://www.yes24.com/Product/Goods/107077663", + contents: + "더 나은 개발자가 되기 위한 실질적인 조언을 담은 개발 철학서의 고전.", price: 35000, - publisher: "인사이트", - publishedAt: "2022-02-18", + salePrice: -1, }, { - bookId: "book-010", title: "그림으로 배우는 네트워크 원리", authors: ["Gene"], - description: - "네트워크의 기초 개념을 풍부한 그림과 함께 쉽게 이해할 수 있도록 설명한다.", - cover: "https://picsum.photos/seed/book10/300/400", + publisher: "영진닷컴", + thumbnail: "https://picsum.photos/seed/book10/300/400", url: "https://www.yes24.com/Product/Goods/104369152", + contents: + "네트워크의 기초 개념을 풍부한 그림과 함께 쉽게 이해할 수 있도록 설명한다.", price: 24000, - publisher: "영진닷컴", - publishedAt: "2022-05-20", - }, - { - bookId: "book-011", - title: "Growing Object-Oriented Software, Guided by Tests", - authors: ["Steve Freeman", "Nat Pryce"], - description: - "TDD를 실전에서 적용하는 방법과 객체지향 설계 원칙을 함께 배울 수 있는 명저.", - cover: "https://picsum.photos/seed/book11/300/400", - url: "https://www.yes24.com/Product/Goods/3551451", - publisher: "Addison-Wesley", - publishedAt: "2009-10-12", - }, - { - bookId: "book-012", - title: "컴퓨터 과학이 보이는 그림책", - authors: ["아다치 마사유키"], - description: - "컴퓨터 과학의 핵심 개념을 그림으로 직관적으로 이해할 수 있도록 구성한 입문서.", - cover: null, - url: "https://www.yes24.com/Product/Goods/66028583", - price: 16000, - publisher: "성안당", - publishedAt: "2019-02-15", + salePrice: 21600, }, ]; export async function fetchRecommendBooks( count?: number, -): Promise { +): Promise { await new Promise((resolve) => setTimeout(resolve, 400)); - return count !== undefined - ? MOCK_RECOMMEND_BOOKS.slice(0, count) - : MOCK_RECOMMEND_BOOKS; + const books = + count !== undefined + ? MOCK_RECOMMEND_BOOKS.slice(0, count) + : MOCK_RECOMMEND_BOOKS; + return { + books, + isPersonalized: true, + message: undefined, + }; } diff --git a/types/myPage.ts b/types/myPage.ts index 6d2f80f..07f245f 100644 --- a/types/myPage.ts +++ b/types/myPage.ts @@ -115,13 +115,18 @@ export interface MyPageRecommendYoutubeResponse { } export interface MyPageRecommendBook { - bookId: string; title: string; authors: string[]; - description?: string; - cover: string | null; - url: string; - price?: number; publisher: string; - publishedAt: string; + thumbnail: string | null; + url: string; + contents: string | null; + price: number; + salePrice: number; +} + +export interface MyPageRecommendBooksResponse { + books: MyPageRecommendBook[]; + isPersonalized: boolean; + message?: string; }