Skip to content

Commit dd70084

Browse files
committed
Merge branch 'main' into feat/#486
2 parents add7a81 + ff66f15 commit dd70084

20 files changed

+572
-37
lines changed

next.config.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const baseURL = process.env.NEXT_PUBLIC_API_URL;
2+
const aladinURL = process.env.ALADIN_OPEN_API_URI;
3+
const ALADIN_API_KEY = process.env.ALADIN_OPEN_API_TTBKEY;
24

35
/** @type {import('next').NextConfig} */
46
const nextConfig = {
@@ -15,6 +17,11 @@ const nextConfig = {
1517
source: '/service-api/:url*',
1618
destination: `${baseURL}/api/:url*`,
1719
},
20+
{
21+
source: '/aladin-api',
22+
has: [{ type: 'query', key: 'QueryType', value: '(?<QueryType>.*)' }],
23+
destination: `${aladinURL}/api/ItemList.aspx?ttbkey=${ALADIN_API_KEY}&QueryType=:QueryType&MaxResults=10&start=1&SearchTarget=Book&output=JS&Version=20131101`,
24+
},
1825
];
1926
},
2027
async redirects() {
@@ -46,6 +53,12 @@ const nextConfig = {
4653
port: '',
4754
pathname: '/**',
4855
},
56+
{
57+
protocol: 'https',
58+
hostname: 'image.aladin.co.kr',
59+
port: '',
60+
pathname: '/**',
61+
},
4962
],
5063
},
5164
};

public/icons/search.svg

Lines changed: 3 additions & 3 deletions
Loading

src/apis/book/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
APISearchedBook,
99
APISearchedBookPagination,
1010
APIRecentSearches,
11+
APIBestSellerRes,
1112
} from '@/types/book';
1213
import bookshelfAPI from '../bookshelf';
1314
import { publicApi } from '../core/axios';
@@ -94,6 +95,11 @@ const bookAPI = {
9495
`/service-api/bookshelves/${bookshelfId}/books/${bookId}`
9596
)
9697
),
98+
99+
getBestSellers: () =>
100+
publicApi.get<APIBestSellerRes>(
101+
`/aladin-api?QueryType=Bestseller&Cover=Big`
102+
),
97103
};
98104

99105
export default bookAPI;

src/app/book/search/page.tsx

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,93 @@
11
'use client';
22

3-
import { VStack, Text } from '@chakra-ui/react';
3+
import { useEffect, useState } from 'react';
4+
import { useInView } from 'react-intersection-observer';
45

5-
import BookSearch from '@/ui/BookSearch';
6+
import useBookSearchQuery from '@/queries/book/useBookSearchQuery';
7+
import useRecentSearchesQuery from '@/queries/book/useRecentSearchesQuery';
8+
9+
import useDebounceValue from '@/hooks/useDebounce';
10+
import { isAuthed } from '@/utils/helpers/auth';
11+
12+
import TopHeader from '@/v1/base/TopHeader';
13+
import BookSearchInput from '@/v1/bookSearch/BookSearchInput';
14+
import BestSellers from '@/v1/bookSearch/BestSellers';
15+
import RecentSearch from '@/v1/bookSearch/RecentSearch';
16+
import BookSearchResults from '@/v1/bookSearch/SearchResult';
17+
18+
/**
19+
* @todo
20+
* recentSearchedInfo 계속해서 refetch되는 현상 고치기
21+
*/
22+
23+
const BookSearch = () => {
24+
const [inputSearchValue, setInputSearchValue] = useState<string>('');
25+
26+
const queryKeyword = useDebounceValue(inputSearchValue, 1000);
27+
28+
const { ref: inViewRef, inView } = useInView();
29+
30+
const bookSearchInfo = useBookSearchQuery({
31+
query: queryKeyword,
32+
page: 1,
33+
pageSize: 12,
34+
});
35+
const recentSearchesInfo = useRecentSearchesQuery({ enabled: isAuthed() });
36+
37+
const searchedBooks = bookSearchInfo.isSuccess
38+
? bookSearchInfo.data.pages.flatMap(page => page.searchBookResponseList)
39+
: [];
40+
const recentSearches = recentSearchesInfo.isSuccess
41+
? recentSearchesInfo.data.bookRecentSearchResponses
42+
: undefined;
43+
44+
const handleInputValueChange = (
45+
event: React.ChangeEvent<HTMLInputElement>
46+
) => {
47+
if (!event.target) return;
48+
49+
const inputValue = event.target.value;
50+
51+
setInputSearchValue(inputValue.trim());
52+
};
53+
54+
useEffect(() => {
55+
if (inView && bookSearchInfo.hasNextPage) {
56+
bookSearchInfo.fetchNextPage();
57+
}
58+
}, [
59+
bookSearchInfo.fetchNextPage,
60+
inView,
61+
bookSearchInfo.hasNextPage,
62+
queryKeyword,
63+
bookSearchInfo,
64+
]);
665

7-
const BookPage = () => {
866
return (
9-
<VStack gap="1rem">
10-
<Text
11-
alignSelf="flex-start"
12-
fontSize="2rem"
13-
fontWeight="800"
14-
color="main"
15-
>
16-
Discover
17-
</Text>
18-
<BookSearch />
19-
</VStack>
67+
<>
68+
<TopHeader text={'Discover'} />
69+
<article className="flex h-full w-full flex-col gap-[3.8rem]">
70+
<BookSearchInput
71+
value={inputSearchValue}
72+
onChange={handleInputValueChange}
73+
/>
74+
{inputSearchValue ? (
75+
<>
76+
<BookSearchResults searchedBooks={searchedBooks} />
77+
<div ref={inViewRef} />
78+
</>
79+
) : (
80+
<>
81+
<RecentSearch
82+
recentSearches={recentSearches}
83+
setInputSearchValue={setInputSearchValue}
84+
/>
85+
<BestSellers />
86+
</>
87+
)}
88+
</article>
89+
</>
2090
);
2191
};
2292

23-
export default BookPage;
93+
export default BookSearch;

src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const RootLayout = ({ children }: { children: ReactNode }) => {
1414
</head>
1515
<body className="app-layout">
1616
<ContextProvider>
17-
<main className={`${LineSeedKR.variable} font-lineseed `}>
17+
<main className={`${LineSeedKR.variable} font-lineseed`}>
1818
{children}
1919
</main>
2020
</ContextProvider>

src/queries/book/key.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const bookKeys = {
55
details: () => [...bookKeys.all, 'detail'] as const,
66
detail: (bookId: APIBook['bookId']) =>
77
[...bookKeys.details(), bookId] as const,
8+
bestSeller: () => [...bookKeys.all, 'bestSeller'],
89
bookmark: (bookId: APIBook['bookId']) =>
910
[...bookKeys.detail(bookId), 'bookmark'] as const,
1011
comments: (bookId: APIBook['bookId']) =>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import type { QueryOptions } from '@/types/query';
3+
4+
import bookAPI from '@/apis/book';
5+
import type { APIBestSellerRes } from '@/types/book';
6+
7+
import bookKeys from './key';
8+
9+
const useBestSellersQuery = (options?: QueryOptions<APIBestSellerRes>) =>
10+
useQuery(
11+
bookKeys.bestSeller(),
12+
() => bookAPI.getBestSellers().then(({ data }) => data),
13+
options
14+
);
15+
16+
export default useBestSellersQuery;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import BestSellers from '@/v1/bookSearch/BestSellers';
3+
4+
const meta: Meta<typeof BestSellers> = {
5+
title: 'bookSearch/BestSellers',
6+
component: BestSellers,
7+
tags: ['autodocs'],
8+
};
9+
10+
export default meta;
11+
12+
type Story = StoryObj<typeof BestSellers>;
13+
14+
type BestSellerProps = {
15+
isbn: string;
16+
title: string;
17+
author: string;
18+
cover: string;
19+
bestRank: number;
20+
link: string;
21+
};
22+
23+
const BESTSELLER: BestSellerProps = {
24+
isbn: '9791162242742',
25+
title: '리팩터링',
26+
author: '마틴 파울러',
27+
cover:
28+
'https://search1.kakaocdn.net/thumb/R120x174.q85/?fname=http%3A%2F%2Ft1.daumcdn.net%2Flbook%2Fimage%2F5326912%3Ftimestamp%3D20231207165435',
29+
bestRank: 1,
30+
link: 'https://search.daum.net/search?w=bookpage&bookId=5326912&q=%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81',
31+
};
32+
33+
export const Default: Story = {
34+
args: {
35+
bestSellers: [BESTSELLER, BESTSELLER, BESTSELLER],
36+
searchRange: 'WEEKLY',
37+
},
38+
};
39+
40+
export const MonthlyBestSeller: Story = {
41+
args: {
42+
bestSellers: [],
43+
searchRange: 'MONTHLY',
44+
},
45+
};
46+
47+
export const YearlyBestSeller: Story = {
48+
args: {
49+
bestSellers: [],
50+
searchRange: 'YEARLY',
51+
},
52+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import BookSearchInput from '@/v1/bookSearch/BookSearchInput';
3+
4+
const meta: Meta<typeof BookSearchInput> = {
5+
title: 'bookSearch/BookSearchInput',
6+
component: BookSearchInput,
7+
tags: ['autodocs'],
8+
};
9+
10+
export default meta;
11+
12+
type Story = StoryObj<typeof BookSearchInput>;
13+
14+
export const Default: Story = {
15+
args: {
16+
value: '',
17+
},
18+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import RecentSearch from '@/v1/bookSearch/RecentSearch';
3+
4+
const meta: Meta<typeof RecentSearch> = {
5+
title: 'bookSearch/RecentSearch',
6+
component: RecentSearch,
7+
tags: ['autodocs'],
8+
};
9+
10+
export default meta;
11+
12+
type Story = StoryObj<typeof RecentSearch>;
13+
14+
export const Default: Story = {
15+
args: {
16+
recentSearches: undefined,
17+
setInputSearchValue: () => alert('선택한 검색어 검색!'),
18+
},
19+
};
20+
21+
export const RecentSearches: Story = {
22+
args: {
23+
recentSearches: [
24+
{ keyword: '21', modifiedAt: 'now' },
25+
{ keyword: 'I Love It', modifiedAt: 'now' },
26+
{ keyword: 'D (Half Moon)', modifiedAt: 'now' },
27+
{ keyword: 'What 2 Do', modifiedAt: 'now' },
28+
{ keyword: 'Bonnie & Clyde', modifiedAt: 'now' },
29+
{ keyword: '풀어', modifiedAt: 'now' },
30+
{ keyword: '어때', modifiedAt: 'now' },
31+
],
32+
setInputSearchValue: () => alert('선택한 검색어 검색!'),
33+
},
34+
};

0 commit comments

Comments
 (0)