From 62ab0372029b8063066d1bbe343b1e715f4d3788 Mon Sep 17 00:00:00 2001 From: 2scent Date: Thu, 16 Jan 2025 00:42:32 +0900 Subject: [PATCH 01/13] =?UTF-8?q?feat:=20Card=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/card.tsx | 58 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/shared/ui/card.tsx diff --git a/src/shared/ui/card.tsx b/src/shared/ui/card.tsx new file mode 100644 index 0000000..fbcb5ad --- /dev/null +++ b/src/shared/ui/card.tsx @@ -0,0 +1,58 @@ +import { forwardRef, HTMLAttributes } from 'react'; +import { cn } from '@/shared/lib/utils'; + +const Card = forwardRef>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = 'Card'; + +const CardHeader = forwardRef>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = 'CardHeader'; + +const CardTitle = forwardRef>(({ className, ...props }, ref) => ( +
+)); +CardTitle.displayName = 'CardTitle'; + +const CardDescription = forwardRef>(({ className, ...props }, ref) => ( +
+)); +CardDescription.displayName = 'CardDescription'; + +const CardContent = forwardRef>(({ className, ...props }, ref) => ( +
+)); +CardContent.displayName = 'CardContent'; + +const CardFooter = forwardRef>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = 'CardFooter'; + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; From 541588b0e79713de049b42c54acfa4a903fe1297 Mon Sep 17 00:00:00 2001 From: 2scent Date: Thu, 16 Jan 2025 00:43:07 +0900 Subject: [PATCH 02/13] =?UTF-8?q?feat:=20Input=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/input.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/shared/ui/input.tsx diff --git a/src/shared/ui/input.tsx b/src/shared/ui/input.tsx new file mode 100644 index 0000000..dad26ff --- /dev/null +++ b/src/shared/ui/input.tsx @@ -0,0 +1,19 @@ +import { ComponentProps, forwardRef } from 'react'; +import { cn } from '@/shared/lib/utils'; + +const Input = forwardRef>(({ className, type, ...props }, ref) => { + return ( + + ); +}); +Input.displayName = 'Input'; + +export { Input }; From 052c2b280ae08226e5b7390d3c7400f1f605b7a2 Mon Sep 17 00:00:00 2001 From: 2scent Date: Thu, 16 Jan 2025 00:46:16 +0900 Subject: [PATCH 03/13] =?UTF-8?q?chore:=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=ED=99=80=EB=8D=94=20svg=20=ED=97=88=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/next.config.ts b/next.config.ts index e9ffa30..d751010 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,15 @@ -import type { NextConfig } from "next"; +import type { NextConfig } from 'next'; const nextConfig: NextConfig = { - /* config options here */ + images: { + dangerouslyAllowSVG: true, + remotePatterns: [ + { + protocol: 'https', + hostname: 'placehold.co', + }, + ], + }, }; export default nextConfig; From 5c39a93145eb4bd7cedd20aaecdc94b13d33ab03 Mon Sep 17 00:00:00 2001 From: 2scent Date: Thu, 16 Jan 2025 00:49:43 +0900 Subject: [PATCH 04/13] =?UTF-8?q?feat:=20=EB=8F=84=EC=84=9C=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/ui/HomePage.tsx | 375 +++++++++++++++++++++++++-------- 1 file changed, 285 insertions(+), 90 deletions(-) diff --git a/src/pages/home/ui/HomePage.tsx b/src/pages/home/ui/HomePage.tsx index 6915f03..6772083 100644 --- a/src/pages/home/ui/HomePage.tsx +++ b/src/pages/home/ui/HomePage.tsx @@ -1,101 +1,296 @@ +'use client'; + +import { useState } from 'react'; import Image from 'next/image'; +import { Sparkles, TrendingUp } from 'lucide-react'; +import { Input } from '@/shared/ui/input'; +import { Card, CardContent } from '@/shared/ui/card'; + +const books = [ + { + id: 1, + title: '클린 코드', + author: '로버트 C. 마틴', + publisher: '인사이트', + publishDate: '2013-12-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 2, + title: '1984', + author: '조지 오웰', + publisher: '민음사', + publishDate: '2009-08-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 3, + title: '해리 포터와 마법사의 돌', + author: 'J.K. 롤링', + publisher: '문학수첩', + publishDate: '2019-11-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 4, + title: '어린 왕자', + author: '생텍쥐페리', + publisher: '열린책들', + publishDate: '2015-10-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 5, + title: '사피엔스', + author: '유발 하라리', + publisher: '김영사', + publishDate: '2015-11-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 6, + title: '데미안', + author: '헤르만 헤세', + publisher: '민음사', + publishDate: '2009-01-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 7, + title: '이것이 자바다', + author: '신용권', + publisher: '한빛미디어', + publishDate: '2022-01-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 8, + title: '파이썬 알고리즘 인터뷰', + author: '박상길', + publisher: '책만', + publishDate: '2020-08-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 9, + title: '토지', + author: '박경리', + publisher: '나남', + publishDate: '2002-03-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 10, + title: '칼의 노래', + author: '김훈', + publisher: '문학동네', + publishDate: '2001-01-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 11, + title: '혼자 공부하는 머신러닝+딥러닝', + author: '박해선', + publisher: '한빛미디어', + publishDate: '2020-12-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 12, + title: '객체지향의 사실과 오해', + author: '조영호', + publisher: '위키북스', + publishDate: '2015-06-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 13, + title: '죽음의 수용소에서', + author: '빅터 프랭클', + publisher: '청아출판사', + publishDate: '2005-08-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 14, + title: '코스모스', + author: '칼 세이건', + publisher: '사이언스북스', + publishDate: '2006-12-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 15, + title: '침묵의 봄', + author: '레이첼 카슨', + publisher: '에코리브르', + publishDate: '2011-10-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 16, + title: '프로그래머의 길, 멘토에게 묻다', + author: '데이브 후버, 애디웨일 오시나이', + publisher: '인사이트', + publishDate: '2010-07-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 17, + title: '맨큐의 경제학', + author: '그레고리 맨큐', + publisher: '센게이지러닝', + publishDate: '2018-01-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 18, + title: '총, 균, 쇠', + author: '재레드 다이아몬드', + publisher: '문학사상사', + publishDate: '2005-12-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 19, + title: '이기적 유전자', + author: '리처드 도킨스', + publisher: '을유문화사', + publishDate: '2018-10-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 20, + title: '연금술사', + author: '파울로 코엘료', + publisher: '문학동네', + publishDate: '2001-04-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 21, + title: '모던 자바스크립트 Deep Dive', + author: '이웅모', + publisher: '위키북스', + publishDate: '2020-09-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 22, + title: '정의란 무엇인가', + author: '마이클 샌델', + publisher: '김영사', + publishDate: '2014-11-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 23, + title: '2024년 트렌드 예측', + author: '김트렌드', + publisher: '미래출판사', + publishDate: '2024-01-15', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 24, + title: '인공지능과 윤리', + author: '이AI', + publisher: '기술과철학', + publishDate: '2023-11-30', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 25, + title: '현대 심리학의 이해', + author: '박마인드', + publisher: '심리학사', + publishDate: '2023-10-05', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 26, + title: '글로벌 경제 전망 2024', + author: '최경제', + publisher: '세계경제연구소', + publishDate: '2023-12-20', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 27, + title: '디지털 전환 시대의 리더십', + author: '정디지털', + publisher: '혁신경영', + publishDate: '2024-02-01', + coverImage: 'https://placehold.co/200x300', + }, +]; export default function HomePage() { + const [filter, setFilter] = useState('new'); + const [searchTerm, setSearchTerm] = useState(''); + + const newBooks = books.filter((book) => book.id % 2 === 0); + const bestsellerBooks = books.filter((book) => book.id % 2 !== 0); + const booksToShow = filter === 'new' ? newBooks : bestsellerBooks; + const filteredBooks = booksToShow.filter( + (book) => + book.title.toLowerCase().includes(searchTerm.toLowerCase()) || + book.author.toLowerCase().includes(searchTerm.toLowerCase()), + ); + return ( -
-
- Next.js logo +
+ setSearchTerm(e.target.value)} /> -
    -
  1. - Get started by editing{' '} - - src/app/page.tsx - - . -
  2. -
  3. Save and see your changes instantly.
  4. -
- - -
- +
+
+ {filteredBooks.map((book) => ( + +
+
+ {`${book.title} +
+ +

{book.title}

+

{book.author}

+

{book.publisher}

+

{book.publishDate}

+
+
+
+ ))} +
); } From a13e8e9a96df5bd35f40dc9a7d97af8e7899abf7 Mon Sep 17 00:00:00 2001 From: 2scent Date: Thu, 16 Jan 2025 21:08:06 +0900 Subject: [PATCH 05/13] =?UTF-8?q?refactor:=20BookCard=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/.gitkeep | 0 src/entities/book/index.ts | 1 + src/entities/book/ui/BookCard.tsx | 34 +++++++++++++++++++++++++++++++ src/pages/home/ui/HomePage.tsx | 27 ++++-------------------- 4 files changed, 39 insertions(+), 23 deletions(-) delete mode 100644 src/entities/.gitkeep create mode 100644 src/entities/book/index.ts create mode 100644 src/entities/book/ui/BookCard.tsx diff --git a/src/entities/.gitkeep b/src/entities/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/entities/book/index.ts b/src/entities/book/index.ts new file mode 100644 index 0000000..124d3e6 --- /dev/null +++ b/src/entities/book/index.ts @@ -0,0 +1 @@ +export { default as BookCard } from './ui/BookCard'; diff --git a/src/entities/book/ui/BookCard.tsx b/src/entities/book/ui/BookCard.tsx new file mode 100644 index 0000000..49d091c --- /dev/null +++ b/src/entities/book/ui/BookCard.tsx @@ -0,0 +1,34 @@ +import Image from 'next/image'; +import { Card, CardContent } from '@/shared/ui/card'; + +interface BookCardProps { + coverImage: string; + title: string; + author: string; + publisher: string; + publishDate: string; +} + +export default function BookCard({ coverImage, title, author, publisher, publishDate }: BookCardProps) { + return ( + +
+
+ {`${title} +
+ +

{title}

+

{author}

+

{publisher}

+

{publishDate}

+
+
+
+ ); +} diff --git a/src/pages/home/ui/HomePage.tsx b/src/pages/home/ui/HomePage.tsx index 6772083..b63ef58 100644 --- a/src/pages/home/ui/HomePage.tsx +++ b/src/pages/home/ui/HomePage.tsx @@ -1,10 +1,9 @@ 'use client'; import { useState } from 'react'; -import Image from 'next/image'; import { Sparkles, TrendingUp } from 'lucide-react'; import { Input } from '@/shared/ui/input'; -import { Card, CardContent } from '@/shared/ui/card'; +import { BookCard } from '@/entities/book'; const books = [ { @@ -267,28 +266,10 @@ export default function HomePage() {
{filteredBooks.map((book) => ( - -
-
- {`${book.title} -
- -

{book.title}

-

{book.author}

-

{book.publisher}

-

{book.publishDate}

-
-
-
+ {...book} + /> ))}
From 052292ec9b7c0c5b34609bee467d92c46ae73248 Mon Sep 17 00:00:00 2001 From: 2scent Date: Thu, 16 Jan 2025 22:33:55 +0900 Subject: [PATCH 06/13] =?UTF-8?q?refactor:=20BookList,=20BookFilter,=20Boo?= =?UTF-8?q?kSearch=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/book/index.ts | 1 + src/entities/book/model/book.ts | 8 + src/entities/book/ui/BookCard.tsx | 11 +- src/entities/book/ui/BookList.tsx | 19 ++ src/features/.gitkeep | 0 src/features/book-filter/index.ts | 2 + src/features/book-filter/model/filter-type.ts | 1 + src/features/book-filter/ui/BookFilter.tsx | 32 ++ src/features/book-search/index.ts | 1 + src/features/book-search/ui/BookSearch.tsx | 18 ++ src/pages/home/model/books.ts | 238 +++++++++++++++ src/pages/home/ui/HomePage.tsx | 276 +----------------- 12 files changed, 339 insertions(+), 268 deletions(-) create mode 100644 src/entities/book/model/book.ts create mode 100644 src/entities/book/ui/BookList.tsx delete mode 100644 src/features/.gitkeep create mode 100644 src/features/book-filter/index.ts create mode 100644 src/features/book-filter/model/filter-type.ts create mode 100644 src/features/book-filter/ui/BookFilter.tsx create mode 100644 src/features/book-search/index.ts create mode 100644 src/features/book-search/ui/BookSearch.tsx create mode 100644 src/pages/home/model/books.ts diff --git a/src/entities/book/index.ts b/src/entities/book/index.ts index 124d3e6..fdac8bb 100644 --- a/src/entities/book/index.ts +++ b/src/entities/book/index.ts @@ -1 +1,2 @@ export { default as BookCard } from './ui/BookCard'; +export { default as BookList } from './ui/BookList'; diff --git a/src/entities/book/model/book.ts b/src/entities/book/model/book.ts new file mode 100644 index 0000000..dca5a86 --- /dev/null +++ b/src/entities/book/model/book.ts @@ -0,0 +1,8 @@ +export interface Book { + id: number; + title: string; + author: string; + publisher: string; + publishDate: string; + coverImage: string; +} diff --git a/src/entities/book/ui/BookCard.tsx b/src/entities/book/ui/BookCard.tsx index 49d091c..2972313 100644 --- a/src/entities/book/ui/BookCard.tsx +++ b/src/entities/book/ui/BookCard.tsx @@ -1,15 +1,14 @@ import Image from 'next/image'; import { Card, CardContent } from '@/shared/ui/card'; +import { Book } from '../model/book'; interface BookCardProps { - coverImage: string; - title: string; - author: string; - publisher: string; - publishDate: string; + book: Book; } -export default function BookCard({ coverImage, title, author, publisher, publishDate }: BookCardProps) { +export default function BookCard({ book }: BookCardProps) { + const { coverImage, title, author, publisher, publishDate } = book; + return (
diff --git a/src/entities/book/ui/BookList.tsx b/src/entities/book/ui/BookList.tsx new file mode 100644 index 0000000..5d14157 --- /dev/null +++ b/src/entities/book/ui/BookList.tsx @@ -0,0 +1,19 @@ +import { Book } from '../model/book'; +import BookCard from './BookCard'; + +interface BookListProps { + books: Book[]; +} + +export default function BookList({ books }: BookListProps) { + return ( +
+ {books.map((book) => ( + + ))} +
+ ); +} diff --git a/src/features/.gitkeep b/src/features/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/features/book-filter/index.ts b/src/features/book-filter/index.ts new file mode 100644 index 0000000..d867c64 --- /dev/null +++ b/src/features/book-filter/index.ts @@ -0,0 +1,2 @@ +export { default as BookFilter } from './ui/BookFilter'; +export type { FilterType } from './model/filter-type'; diff --git a/src/features/book-filter/model/filter-type.ts b/src/features/book-filter/model/filter-type.ts new file mode 100644 index 0000000..90c4f3b --- /dev/null +++ b/src/features/book-filter/model/filter-type.ts @@ -0,0 +1 @@ +export type FilterType = 'new' | 'bestseller'; diff --git a/src/features/book-filter/ui/BookFilter.tsx b/src/features/book-filter/ui/BookFilter.tsx new file mode 100644 index 0000000..24949c2 --- /dev/null +++ b/src/features/book-filter/ui/BookFilter.tsx @@ -0,0 +1,32 @@ +import { Sparkles, TrendingUp } from 'lucide-react'; +import { FilterType } from '../model/filter-type'; + +interface BookFilterProps { + currentFilter: FilterType; + onFilterChange: (filter: FilterType) => void; +} + +export default function BookFilter({ currentFilter, onFilterChange }: BookFilterProps) { + return ( +
+ + +
+ ); +} diff --git a/src/features/book-search/index.ts b/src/features/book-search/index.ts new file mode 100644 index 0000000..29f3ef2 --- /dev/null +++ b/src/features/book-search/index.ts @@ -0,0 +1 @@ +export { default as BookSearch } from './ui/BookSearch'; diff --git a/src/features/book-search/ui/BookSearch.tsx b/src/features/book-search/ui/BookSearch.tsx new file mode 100644 index 0000000..4abc407 --- /dev/null +++ b/src/features/book-search/ui/BookSearch.tsx @@ -0,0 +1,18 @@ +import { Input } from '@/shared/ui/input'; + +interface BookSearchProps { + searchTerm: string; + onSearch: (term: string) => void; +} + +export default function BookSearch({ searchTerm, onSearch }: BookSearchProps) { + return ( + onSearch(e.target.value)} + /> + ); +} diff --git a/src/pages/home/model/books.ts b/src/pages/home/model/books.ts new file mode 100644 index 0000000..3922ac7 --- /dev/null +++ b/src/pages/home/model/books.ts @@ -0,0 +1,238 @@ +import type { FilterType } from '@/features/book-filter'; + +const books = [ + { + id: 1, + title: '클린 코드', + author: '로버트 C. 마틴', + publisher: '인사이트', + publishDate: '2013-12-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 2, + title: '1984', + author: '조지 오웰', + publisher: '민음사', + publishDate: '2009-08-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 3, + title: '해리 포터와 마법사의 돌', + author: 'J.K. 롤링', + publisher: '문학수첩', + publishDate: '2019-11-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 4, + title: '어린 왕자', + author: '생텍쥐페리', + publisher: '열린책들', + publishDate: '2015-10-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 5, + title: '사피엔스', + author: '유발 하라리', + publisher: '김영사', + publishDate: '2015-11-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 6, + title: '데미안', + author: '헤르만 헤세', + publisher: '민음사', + publishDate: '2009-01-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 7, + title: '이것이 자바다', + author: '신용권', + publisher: '한빛미디어', + publishDate: '2022-01-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 8, + title: '파이썬 알고리즘 인터뷰', + author: '박상길', + publisher: '책만', + publishDate: '2020-08-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 9, + title: '토지', + author: '박경리', + publisher: '나남', + publishDate: '2002-03-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 10, + title: '칼의 노래', + author: '김훈', + publisher: '문학동네', + publishDate: '2001-01-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 11, + title: '혼자 공부하는 머신러닝+딥러닝', + author: '박해선', + publisher: '한빛미디어', + publishDate: '2020-12-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 12, + title: '객체지향의 사실과 오해', + author: '조영호', + publisher: '위키북스', + publishDate: '2015-06-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 13, + title: '죽음의 수용소에서', + author: '빅터 프랭클', + publisher: '청아출판사', + publishDate: '2005-08-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 14, + title: '코스모스', + author: '칼 세이건', + publisher: '사이언스북스', + publishDate: '2006-12-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 15, + title: '침묵의 봄', + author: '레이첼 카슨', + publisher: '에코리브르', + publishDate: '2011-10-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 16, + title: '프로그래머의 길, 멘토에게 묻다', + author: '데이브 후버, 애디웨일 오시나이', + publisher: '인사이트', + publishDate: '2010-07-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 17, + title: '맨큐의 경제학', + author: '그레고리 맨큐', + publisher: '센게이지러닝', + publishDate: '2018-01-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 18, + title: '총, 균, 쇠', + author: '재레드 다이아몬드', + publisher: '문학사상사', + publishDate: '2005-12-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 19, + title: '이기적 유전자', + author: '리처드 도킨스', + publisher: '을유문화사', + publishDate: '2018-10-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 20, + title: '연금술사', + author: '파울로 코엘료', + publisher: '문학동네', + publishDate: '2001-04-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 21, + title: '모던 자바스크립트 Deep Dive', + author: '이웅모', + publisher: '위키북스', + publishDate: '2020-09-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 22, + title: '정의란 무엇인가', + author: '마이클 샌델', + publisher: '김영사', + publishDate: '2014-11-01', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 23, + title: '2024년 트렌드 예측', + author: '김트렌드', + publisher: '미래출판사', + publishDate: '2024-01-15', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 24, + title: '인공지능과 윤리', + author: '이AI', + publisher: '기술과철학', + publishDate: '2023-11-30', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 25, + title: '현대 심리학의 이해', + author: '박마인드', + publisher: '심리학사', + publishDate: '2023-10-05', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 26, + title: '글로벌 경제 전망 2024', + author: '최경제', + publisher: '세계경제연구소', + publishDate: '2023-12-20', + coverImage: 'https://placehold.co/200x300', + }, + { + id: 27, + title: '디지털 전환 시대의 리더십', + author: '정디지털', + publisher: '혁신경영', + publishDate: '2024-02-01', + coverImage: 'https://placehold.co/200x300', + }, +]; + +interface UseBooksOptions { + filter: FilterType; + searchTerm: string; +} + +export function useBooks({ filter, searchTerm }: UseBooksOptions) { + const newBooks = books.filter((book) => book.id % 2 === 0); + const bestsellerBooks = books.filter((book) => book.id % 2 !== 0); + + const booksToShow = filter === 'new' ? newBooks : bestsellerBooks; + + return booksToShow.filter( + (book) => + book.title.toLowerCase().includes(searchTerm.toLowerCase()) || + book.author.toLowerCase().includes(searchTerm.toLowerCase()), + ); +} diff --git a/src/pages/home/ui/HomePage.tsx b/src/pages/home/ui/HomePage.tsx index b63ef58..0b1d445 100644 --- a/src/pages/home/ui/HomePage.tsx +++ b/src/pages/home/ui/HomePage.tsx @@ -1,277 +1,29 @@ 'use client'; import { useState } from 'react'; -import { Sparkles, TrendingUp } from 'lucide-react'; -import { Input } from '@/shared/ui/input'; -import { BookCard } from '@/entities/book'; - -const books = [ - { - id: 1, - title: '클린 코드', - author: '로버트 C. 마틴', - publisher: '인사이트', - publishDate: '2013-12-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 2, - title: '1984', - author: '조지 오웰', - publisher: '민음사', - publishDate: '2009-08-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 3, - title: '해리 포터와 마법사의 돌', - author: 'J.K. 롤링', - publisher: '문학수첩', - publishDate: '2019-11-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 4, - title: '어린 왕자', - author: '생텍쥐페리', - publisher: '열린책들', - publishDate: '2015-10-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 5, - title: '사피엔스', - author: '유발 하라리', - publisher: '김영사', - publishDate: '2015-11-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 6, - title: '데미안', - author: '헤르만 헤세', - publisher: '민음사', - publishDate: '2009-01-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 7, - title: '이것이 자바다', - author: '신용권', - publisher: '한빛미디어', - publishDate: '2022-01-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 8, - title: '파이썬 알고리즘 인터뷰', - author: '박상길', - publisher: '책만', - publishDate: '2020-08-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 9, - title: '토지', - author: '박경리', - publisher: '나남', - publishDate: '2002-03-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 10, - title: '칼의 노래', - author: '김훈', - publisher: '문학동네', - publishDate: '2001-01-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 11, - title: '혼자 공부하는 머신러닝+딥러닝', - author: '박해선', - publisher: '한빛미디어', - publishDate: '2020-12-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 12, - title: '객체지향의 사실과 오해', - author: '조영호', - publisher: '위키북스', - publishDate: '2015-06-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 13, - title: '죽음의 수용소에서', - author: '빅터 프랭클', - publisher: '청아출판사', - publishDate: '2005-08-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 14, - title: '코스모스', - author: '칼 세이건', - publisher: '사이언스북스', - publishDate: '2006-12-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 15, - title: '침묵의 봄', - author: '레이첼 카슨', - publisher: '에코리브르', - publishDate: '2011-10-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 16, - title: '프로그래머의 길, 멘토에게 묻다', - author: '데이브 후버, 애디웨일 오시나이', - publisher: '인사이트', - publishDate: '2010-07-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 17, - title: '맨큐의 경제학', - author: '그레고리 맨큐', - publisher: '센게이지러닝', - publishDate: '2018-01-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 18, - title: '총, 균, 쇠', - author: '재레드 다이아몬드', - publisher: '문학사상사', - publishDate: '2005-12-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 19, - title: '이기적 유전자', - author: '리처드 도킨스', - publisher: '을유문화사', - publishDate: '2018-10-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 20, - title: '연금술사', - author: '파울로 코엘료', - publisher: '문학동네', - publishDate: '2001-04-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 21, - title: '모던 자바스크립트 Deep Dive', - author: '이웅모', - publisher: '위키북스', - publishDate: '2020-09-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 22, - title: '정의란 무엇인가', - author: '마이클 샌델', - publisher: '김영사', - publishDate: '2014-11-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 23, - title: '2024년 트렌드 예측', - author: '김트렌드', - publisher: '미래출판사', - publishDate: '2024-01-15', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 24, - title: '인공지능과 윤리', - author: '이AI', - publisher: '기술과철학', - publishDate: '2023-11-30', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 25, - title: '현대 심리학의 이해', - author: '박마인드', - publisher: '심리학사', - publishDate: '2023-10-05', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 26, - title: '글로벌 경제 전망 2024', - author: '최경제', - publisher: '세계경제연구소', - publishDate: '2023-12-20', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 27, - title: '디지털 전환 시대의 리더십', - author: '정디지털', - publisher: '혁신경영', - publishDate: '2024-02-01', - coverImage: 'https://placehold.co/200x300', - }, -]; +import { BookList } from '@/entities/book'; +import { BookFilter, FilterType } from '@/features/book-filter'; +import { BookSearch } from '@/features/book-search'; +import { useBooks } from '../model/books'; export default function HomePage() { - const [filter, setFilter] = useState('new'); + const [filter, setFilter] = useState('new'); const [searchTerm, setSearchTerm] = useState(''); - - const newBooks = books.filter((book) => book.id % 2 === 0); - const bestsellerBooks = books.filter((book) => book.id % 2 !== 0); - const booksToShow = filter === 'new' ? newBooks : bestsellerBooks; - const filteredBooks = booksToShow.filter( - (book) => - book.title.toLowerCase().includes(searchTerm.toLowerCase()) || - book.author.toLowerCase().includes(searchTerm.toLowerCase()), - ); + const books = useBooks({ filter, searchTerm }); return (
- setSearchTerm(e.target.value)} + + -
- - -
-
-
- {filteredBooks.map((book) => ( - - ))}
+
); } From 7cc5e1a4c75fdb9e86113b5ee779727cb9e22cc7 Mon Sep 17 00:00:00 2001 From: 2scent Date: Mon, 20 Jan 2025 22:54:36 +0900 Subject: [PATCH 07/13] =?UTF-8?q?feat:=20=EC=95=8C=EB=9D=BC=EB=94=98=20API?= =?UTF-8?q?=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/book/index.ts | 1 + src/pages/home/api/dto.ts | 23 +++ src/pages/home/api/fetch-books.ts | 24 +++ src/pages/home/api/mapper.ts | 13 ++ src/pages/home/model/books.ts | 238 ----------------------------- src/pages/home/ui/BookControls.tsx | 23 +++ src/pages/home/ui/HomePage.tsx | 25 +-- 7 files changed, 89 insertions(+), 258 deletions(-) create mode 100644 src/pages/home/api/dto.ts create mode 100644 src/pages/home/api/fetch-books.ts create mode 100644 src/pages/home/api/mapper.ts delete mode 100644 src/pages/home/model/books.ts create mode 100644 src/pages/home/ui/BookControls.tsx diff --git a/src/entities/book/index.ts b/src/entities/book/index.ts index fdac8bb..605150a 100644 --- a/src/entities/book/index.ts +++ b/src/entities/book/index.ts @@ -1,2 +1,3 @@ export { default as BookCard } from './ui/BookCard'; export { default as BookList } from './ui/BookList'; +export type { Book } from './model/book'; diff --git a/src/pages/home/api/dto.ts b/src/pages/home/api/dto.ts new file mode 100644 index 0000000..1152a1e --- /dev/null +++ b/src/pages/home/api/dto.ts @@ -0,0 +1,23 @@ +export interface BookDTO { + title: string; + link: string; + author: string; + pubDate: string; + description: string; + isbn: string; + isbn13: string; + itemId: number; + priceSales: number; + priceStandard: number; + mallType: string; + stockStatus: string; + mileage: number; + cover: string; + categoryId: number; + categoryName: string; + publisher: string; + salesPoint: number; + adult: boolean; + fixedPrice: boolean; + customerReviewRank: number; +} diff --git a/src/pages/home/api/fetch-books.ts b/src/pages/home/api/fetch-books.ts new file mode 100644 index 0000000..3626733 --- /dev/null +++ b/src/pages/home/api/fetch-books.ts @@ -0,0 +1,24 @@ +import { BookDTO } from './dto'; +import { adaptBookDTO } from './mapper'; + +interface Response { + version: string; + title: string; + link: string; + pubDate: string; + totalResults: number; + startIndex: number; + itemsPerPage: number; + query: string; + searchCategoryId: number; + searchCategoryName: string; + item: BookDTO[]; +} + +export async function fetchBooks() { + const response = await fetch( + 'https://www.aladin.co.kr/ttb/api/ItemList.aspx?ttbkey=ttbant12742104001&QueryType=ItemNewAll&MaxResults=10&start=1&SearchTarget=Book&output=js&Version=20131101', + ); + const data = (await response.json()) as Response; + return data.item.map(adaptBookDTO); +} diff --git a/src/pages/home/api/mapper.ts b/src/pages/home/api/mapper.ts new file mode 100644 index 0000000..47f40f8 --- /dev/null +++ b/src/pages/home/api/mapper.ts @@ -0,0 +1,13 @@ +import { Book } from '@/entities/book'; +import { BookDTO } from './dto'; + +export function adaptBookDTO(dto: BookDTO): Book { + return { + id: dto.itemId, + title: dto.title, + author: dto.author, + publisher: dto.publisher, + publishDate: dto.pubDate, + coverImage: dto.cover, + }; +} diff --git a/src/pages/home/model/books.ts b/src/pages/home/model/books.ts deleted file mode 100644 index 3922ac7..0000000 --- a/src/pages/home/model/books.ts +++ /dev/null @@ -1,238 +0,0 @@ -import type { FilterType } from '@/features/book-filter'; - -const books = [ - { - id: 1, - title: '클린 코드', - author: '로버트 C. 마틴', - publisher: '인사이트', - publishDate: '2013-12-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 2, - title: '1984', - author: '조지 오웰', - publisher: '민음사', - publishDate: '2009-08-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 3, - title: '해리 포터와 마법사의 돌', - author: 'J.K. 롤링', - publisher: '문학수첩', - publishDate: '2019-11-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 4, - title: '어린 왕자', - author: '생텍쥐페리', - publisher: '열린책들', - publishDate: '2015-10-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 5, - title: '사피엔스', - author: '유발 하라리', - publisher: '김영사', - publishDate: '2015-11-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 6, - title: '데미안', - author: '헤르만 헤세', - publisher: '민음사', - publishDate: '2009-01-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 7, - title: '이것이 자바다', - author: '신용권', - publisher: '한빛미디어', - publishDate: '2022-01-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 8, - title: '파이썬 알고리즘 인터뷰', - author: '박상길', - publisher: '책만', - publishDate: '2020-08-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 9, - title: '토지', - author: '박경리', - publisher: '나남', - publishDate: '2002-03-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 10, - title: '칼의 노래', - author: '김훈', - publisher: '문학동네', - publishDate: '2001-01-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 11, - title: '혼자 공부하는 머신러닝+딥러닝', - author: '박해선', - publisher: '한빛미디어', - publishDate: '2020-12-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 12, - title: '객체지향의 사실과 오해', - author: '조영호', - publisher: '위키북스', - publishDate: '2015-06-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 13, - title: '죽음의 수용소에서', - author: '빅터 프랭클', - publisher: '청아출판사', - publishDate: '2005-08-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 14, - title: '코스모스', - author: '칼 세이건', - publisher: '사이언스북스', - publishDate: '2006-12-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 15, - title: '침묵의 봄', - author: '레이첼 카슨', - publisher: '에코리브르', - publishDate: '2011-10-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 16, - title: '프로그래머의 길, 멘토에게 묻다', - author: '데이브 후버, 애디웨일 오시나이', - publisher: '인사이트', - publishDate: '2010-07-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 17, - title: '맨큐의 경제학', - author: '그레고리 맨큐', - publisher: '센게이지러닝', - publishDate: '2018-01-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 18, - title: '총, 균, 쇠', - author: '재레드 다이아몬드', - publisher: '문학사상사', - publishDate: '2005-12-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 19, - title: '이기적 유전자', - author: '리처드 도킨스', - publisher: '을유문화사', - publishDate: '2018-10-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 20, - title: '연금술사', - author: '파울로 코엘료', - publisher: '문학동네', - publishDate: '2001-04-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 21, - title: '모던 자바스크립트 Deep Dive', - author: '이웅모', - publisher: '위키북스', - publishDate: '2020-09-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 22, - title: '정의란 무엇인가', - author: '마이클 샌델', - publisher: '김영사', - publishDate: '2014-11-01', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 23, - title: '2024년 트렌드 예측', - author: '김트렌드', - publisher: '미래출판사', - publishDate: '2024-01-15', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 24, - title: '인공지능과 윤리', - author: '이AI', - publisher: '기술과철학', - publishDate: '2023-11-30', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 25, - title: '현대 심리학의 이해', - author: '박마인드', - publisher: '심리학사', - publishDate: '2023-10-05', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 26, - title: '글로벌 경제 전망 2024', - author: '최경제', - publisher: '세계경제연구소', - publishDate: '2023-12-20', - coverImage: 'https://placehold.co/200x300', - }, - { - id: 27, - title: '디지털 전환 시대의 리더십', - author: '정디지털', - publisher: '혁신경영', - publishDate: '2024-02-01', - coverImage: 'https://placehold.co/200x300', - }, -]; - -interface UseBooksOptions { - filter: FilterType; - searchTerm: string; -} - -export function useBooks({ filter, searchTerm }: UseBooksOptions) { - const newBooks = books.filter((book) => book.id % 2 === 0); - const bestsellerBooks = books.filter((book) => book.id % 2 !== 0); - - const booksToShow = filter === 'new' ? newBooks : bestsellerBooks; - - return booksToShow.filter( - (book) => - book.title.toLowerCase().includes(searchTerm.toLowerCase()) || - book.author.toLowerCase().includes(searchTerm.toLowerCase()), - ); -} diff --git a/src/pages/home/ui/BookControls.tsx b/src/pages/home/ui/BookControls.tsx new file mode 100644 index 0000000..58b5fc7 --- /dev/null +++ b/src/pages/home/ui/BookControls.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { useState } from 'react'; +import { BookFilter, FilterType } from '@/features/book-filter'; +import { BookSearch } from '@/features/book-search'; + +export default function BookControls() { + const [filter, setFilter] = useState('new'); + const [searchTerm, setSearchTerm] = useState(''); + + return ( +
+ + +
+ ); +} diff --git a/src/pages/home/ui/HomePage.tsx b/src/pages/home/ui/HomePage.tsx index 0b1d445..571657b 100644 --- a/src/pages/home/ui/HomePage.tsx +++ b/src/pages/home/ui/HomePage.tsx @@ -1,28 +1,13 @@ -'use client'; - -import { useState } from 'react'; import { BookList } from '@/entities/book'; -import { BookFilter, FilterType } from '@/features/book-filter'; -import { BookSearch } from '@/features/book-search'; -import { useBooks } from '../model/books'; +import { fetchBooks } from '../api/fetch-books'; +import BookControls from './BookControls'; -export default function HomePage() { - const [filter, setFilter] = useState('new'); - const [searchTerm, setSearchTerm] = useState(''); - const books = useBooks({ filter, searchTerm }); +export default async function HomePage() { + const books = await fetchBooks(); return (
-
- - -
+
); From bbba0c54d85a00b4550fd36a9fcbf28dd8e206db Mon Sep 17 00:00:00 2001 From: 2scent Date: Mon, 20 Jan 2025 22:56:34 +0900 Subject: [PATCH 08/13] =?UTF-8?q?chore:=20=EC=95=8C=EB=9D=BC=EB=94=98=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=97=88=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/next.config.ts b/next.config.ts index d751010..3a7cec8 100644 --- a/next.config.ts +++ b/next.config.ts @@ -8,6 +8,10 @@ const nextConfig: NextConfig = { protocol: 'https', hostname: 'placehold.co', }, + { + protocol: 'https', + hostname: 'image.aladin.co.kr', + }, ], }, }; From 55a20688a7846dd79388ca23f49ba2c224682807 Mon Sep 17 00:00:00 2001 From: 2scent Date: Mon, 20 Jan 2025 23:31:43 +0900 Subject: [PATCH 09/13] =?UTF-8?q?refactor:=20ky=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=ED=95=B4=EC=84=9C=20=EC=95=8C=EB=9D=BC=EB=94=98=20api=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 9 +++++++++ src/pages/home/api/fetch-books.ts | 15 +++++++++++---- src/shared/api/aladin-client.ts | 15 +++++++++++++++ src/shared/config/aladin.ts | 2 ++ 5 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 src/shared/api/aladin-client.ts create mode 100644 src/shared/config/aladin.ts diff --git a/package.json b/package.json index 7ed80b3..a147bb6 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "ky": "^1.7.4", "lucide-react": "^0.471.0", "next": "15.1.4", "react": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7eebf20..8480d00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + ky: + specifier: ^1.7.4 + version: 1.7.4 lucide-react: specifier: ^0.471.0 version: 0.471.0(react@19.0.0) @@ -1216,6 +1219,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + ky@1.7.4: + resolution: {integrity: sha512-zYEr/gh7uLW2l4su11bmQ2M9xLgQLjyvx58UyNM/6nuqyWFHPX5ktMjvpev3F8QWdjSsHUpnWew4PBCswBNuMQ==} + engines: {node: '>=18'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -3227,6 +3234,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + ky@1.7.4: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: diff --git a/src/pages/home/api/fetch-books.ts b/src/pages/home/api/fetch-books.ts index 3626733..334827a 100644 --- a/src/pages/home/api/fetch-books.ts +++ b/src/pages/home/api/fetch-books.ts @@ -1,3 +1,4 @@ +import { aladinApi } from '@/shared/api/aladin-client'; import { BookDTO } from './dto'; import { adaptBookDTO } from './mapper'; @@ -16,9 +17,15 @@ interface Response { } export async function fetchBooks() { - const response = await fetch( - 'https://www.aladin.co.kr/ttb/api/ItemList.aspx?ttbkey=ttbant12742104001&QueryType=ItemNewAll&MaxResults=10&start=1&SearchTarget=Book&output=js&Version=20131101', - ); - const data = (await response.json()) as Response; + const searchParams = new URLSearchParams({ + QueryType: 'ItemNewAll', + MaxResults: '10', + start: '1', + SearchTarget: 'Book', + output: 'js', + Version: '20131101', + }); + + const data = await aladinApi.get('ItemList.aspx', { searchParams }).json(); return data.item.map(adaptBookDTO); } diff --git a/src/shared/api/aladin-client.ts b/src/shared/api/aladin-client.ts new file mode 100644 index 0000000..1a7f5a1 --- /dev/null +++ b/src/shared/api/aladin-client.ts @@ -0,0 +1,15 @@ +import ky from 'ky'; +import { ALADIN_API_KEY, ALADIN_API_URL } from '@/shared/config/aladin'; + +export const aladinApi = ky.create({ + prefixUrl: ALADIN_API_URL, + hooks: { + beforeRequest: [ + async (request) => { + const url = new URL(request.url); + url.searchParams.set('ttbkey', ALADIN_API_KEY); + return new Request(url, request); + }, + ], + }, +}); diff --git a/src/shared/config/aladin.ts b/src/shared/config/aladin.ts new file mode 100644 index 0000000..191d466 --- /dev/null +++ b/src/shared/config/aladin.ts @@ -0,0 +1,2 @@ +export const ALADIN_API_KEY = 'ttbant12742104001'; +export const ALADIN_API_URL = 'https://www.aladin.co.kr/ttb/api'; From 414c499bb2188e6145189ef1016ab8976ee22c2a Mon Sep 17 00:00:00 2001 From: 2scent Date: Mon, 20 Jan 2025 23:35:19 +0900 Subject: [PATCH 10/13] =?UTF-8?q?feat:=20Button=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 34 +++++++++++++++++++++++++++ src/shared/ui/button.tsx | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 src/shared/ui/button.tsx diff --git a/package.json b/package.json index a147bb6..23b1d53 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ ] }, "dependencies": { + "@radix-ui/react-slot": "^1.1.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "ky": "^1.7.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8480d00..bea198a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@radix-ui/react-slot': + specifier: ^1.1.1 + version: 1.1.1(@types/react@19.0.4)(react@19.0.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -346,6 +349,24 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/react-compose-refs@1.1.1': + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.1.1': + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -2157,6 +2178,19 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.4)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.4 + + '@radix-ui/react-slot@1.1.1(@types/react@19.0.4)(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.4)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.4 + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.10.5': {} diff --git a/src/shared/ui/button.tsx b/src/shared/ui/button.tsx new file mode 100644 index 0000000..38aa345 --- /dev/null +++ b/src/shared/ui/button.tsx @@ -0,0 +1,50 @@ +import { ButtonHTMLAttributes, forwardRef } from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/shared/lib/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'size-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +); + +export interface ButtonProps extends ButtonHTMLAttributes, VariantProps { + asChild?: boolean; +} + +const Button = forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + }, +); +Button.displayName = 'Button'; + +export { Button, buttonVariants }; From 48c55e79c8aca908600485b7e7f998a93fe7351c Mon Sep 17 00:00:00 2001 From: 2scent Date: Tue, 21 Jan 2025 00:31:05 +0900 Subject: [PATCH 11/13] =?UTF-8?q?feat:=20=EB=8F=84=EC=84=9C=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/book-search/ui/BookSearch.tsx | 49 +++++++++++++++++----- src/pages/home/api/fetch-books.ts | 28 +++++++++++-- src/pages/home/ui/BookControls.tsx | 12 +++--- src/pages/home/ui/HomePage.tsx | 9 ++-- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/features/book-search/ui/BookSearch.tsx b/src/features/book-search/ui/BookSearch.tsx index 4abc407..c74bf57 100644 --- a/src/features/book-search/ui/BookSearch.tsx +++ b/src/features/book-search/ui/BookSearch.tsx @@ -1,18 +1,47 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Button } from '@/shared/ui/button'; import { Input } from '@/shared/ui/input'; interface BookSearchProps { - searchTerm: string; - onSearch: (term: string) => void; + initialSearchTerm?: string; } -export default function BookSearch({ searchTerm, onSearch }: BookSearchProps) { +export default function BookSearch({ initialSearchTerm = '' }: BookSearchProps) { + const [searchTerm, setSearchTerm] = useState(initialSearchTerm); + + const router = useRouter(); + + const handleChange = (e: React.ChangeEvent) => { + setSearchTerm(e.target.value); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + router.push(`/?q=${searchTerm}`); + }; + return ( - onSearch(e.target.value)} - /> +
+ + +
); } diff --git a/src/pages/home/api/fetch-books.ts b/src/pages/home/api/fetch-books.ts index 334827a..5119d58 100644 --- a/src/pages/home/api/fetch-books.ts +++ b/src/pages/home/api/fetch-books.ts @@ -16,7 +16,17 @@ interface Response { item: BookDTO[]; } -export async function fetchBooks() { +interface FetchBooksParams { + q?: string; +} + +export async function fetchBooks({ q }: FetchBooksParams) { + const response = q ? fetchSearchBooks(q) : fetchNewBooks(); + const data = await response.json(); + return data.item.map(adaptBookDTO); +} + +function fetchNewBooks() { const searchParams = new URLSearchParams({ QueryType: 'ItemNewAll', MaxResults: '10', @@ -26,6 +36,18 @@ export async function fetchBooks() { Version: '20131101', }); - const data = await aladinApi.get('ItemList.aspx', { searchParams }).json(); - return data.item.map(adaptBookDTO); + return aladinApi.get('ItemList.aspx', { searchParams }); +} + +function fetchSearchBooks(q: string) { + const searchParams = new URLSearchParams({ + Query: q, + MaxResults: '10', + start: '1', + SearchTarget: 'Book', + output: 'js', + Version: '20131101', + }); + + return aladinApi.get('ItemSearch.aspx', { searchParams }); } diff --git a/src/pages/home/ui/BookControls.tsx b/src/pages/home/ui/BookControls.tsx index 58b5fc7..b86d8ad 100644 --- a/src/pages/home/ui/BookControls.tsx +++ b/src/pages/home/ui/BookControls.tsx @@ -4,16 +4,16 @@ import { useState } from 'react'; import { BookFilter, FilterType } from '@/features/book-filter'; import { BookSearch } from '@/features/book-search'; -export default function BookControls() { +interface BookControlsProps { + initialSearchTerm?: string; +} + +export default function BookControls({ initialSearchTerm }: BookControlsProps) { const [filter, setFilter] = useState('new'); - const [searchTerm, setSearchTerm] = useState(''); return (
- + ; + +export default async function HomePage(props: { searchParams: SearchParams }) { + const { q } = await props.searchParams; + const books = await fetchBooks({ q }); return (
- +
); From d76ce536e98fcbd6a9c0f78c3dc8796a47ef7330 Mon Sep 17 00:00:00 2001 From: 2scent Date: Tue, 21 Jan 2025 00:38:26 +0900 Subject: [PATCH 12/13] =?UTF-8?q?feat:=20=EC=95=8C=EB=9D=BC=EB=94=98=20?= =?UTF-8?q?=ED=91=9C=EC=A7=80=20=ED=92=88=EC=A7=88=20=ED=96=A5=EC=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/api/fetch-books.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/pages/home/api/fetch-books.ts b/src/pages/home/api/fetch-books.ts index 5119d58..3262c00 100644 --- a/src/pages/home/api/fetch-books.ts +++ b/src/pages/home/api/fetch-books.ts @@ -26,14 +26,19 @@ export async function fetchBooks({ q }: FetchBooksParams) { return data.item.map(adaptBookDTO); } +const baseSearchParams = { + MaxResults: '10', + start: '1', + SearchTarget: 'Book', + Cover: 'Big', + output: 'js', + Version: '20131101', +}; + function fetchNewBooks() { const searchParams = new URLSearchParams({ + ...baseSearchParams, QueryType: 'ItemNewAll', - MaxResults: '10', - start: '1', - SearchTarget: 'Book', - output: 'js', - Version: '20131101', }); return aladinApi.get('ItemList.aspx', { searchParams }); @@ -41,12 +46,8 @@ function fetchNewBooks() { function fetchSearchBooks(q: string) { const searchParams = new URLSearchParams({ + ...baseSearchParams, Query: q, - MaxResults: '10', - start: '1', - SearchTarget: 'Book', - output: 'js', - Version: '20131101', }); return aladinApi.get('ItemSearch.aspx', { searchParams }); From eafcf9efc3dbed453ca203773801b754555e7ef5 Mon Sep 17 00:00:00 2001 From: 2scent Date: Tue, 21 Jan 2025 00:45:50 +0900 Subject: [PATCH 13/13] =?UTF-8?q?feat:=20BookCard=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EB=B0=98=EC=9D=91=ED=98=95=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/book/ui/BookCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entities/book/ui/BookCard.tsx b/src/entities/book/ui/BookCard.tsx index 2972313..f11134d 100644 --- a/src/entities/book/ui/BookCard.tsx +++ b/src/entities/book/ui/BookCard.tsx @@ -18,7 +18,7 @@ export default function BookCard({ book }: BookCardProps) { alt={`${title} 표지`} width={200} height={300} - className="h-32 w-full object-cover sm:h-64" + className="size-full object-contain sm:h-64 sm:object-cover" />