diff --git a/src/api/booksnap.api.ts b/src/api/booksnap.api.ts
index 8a818a5..3938b1e 100644
--- a/src/api/booksnap.api.ts
+++ b/src/api/booksnap.api.ts
@@ -115,3 +115,42 @@ export const searchBookstore = async (query: string | null) => {
console.log(error);
}
};
+
+// 책 리뷰검색하기
+export const searchReview = async (bookName: string) => {
+ try {
+ const response = await instance.get(`/api/search?bookName=${bookName}`);
+ if (response.status === 200) {
+ return response.data;
+ }
+ } catch (err) {
+ console.log(err);
+ }
+};
+
+// 책 검색 기록 저장
+export const postSearchHistory = async (searchType: string, searchWord: string) => {
+ try {
+ const response = await instance.post(`/api/search-history`, {
+ searchType: searchType,
+ searchWord: searchWord,
+ });
+ if (response.status === 200) {
+ return response.data;
+ }
+ } catch (err) {
+ console.log(err);
+ }
+};
+
+// 최근 검색어 불러오기
+export const getRecentSearch = async (searchtype: string, page: number, size: number) => {
+ try {
+ const response = await instance.get(`/api/search-history?searchtype=${searchtype}&page=${page}&size=${size}`);
+ if (response.status === 200) {
+ return response.data;
+ }
+ } catch (err) {
+ console.log(err);
+ }
+};
diff --git a/src/api/instance.ts b/src/api/instance.ts
index b84d20b..f12f8be 100644
--- a/src/api/instance.ts
+++ b/src/api/instance.ts
@@ -24,10 +24,11 @@ instance.interceptors.request.use(
// 응답 인터셉터
instance.interceptors.response.use(
(response) => {
- const { accessToken, refreshToken } = response.data.data || {};
+ const { accessToken, refreshToken, nickname } = response.data.data || {};
if (accessToken && refreshToken) {
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
+ localStorage.setItem('nickname', nickname);
}
return response;
},
@@ -57,6 +58,7 @@ instance.interceptors.response.use(
console.log('Token 갱신 실패:', refreshErr);
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
+ localStorage.removeItem('nickname');
window.location.href = '/login';
return Promise.reject(refreshErr);
}
diff --git a/src/api/zip.api.ts b/src/api/zip.api.ts
index e2e113c..a21ee85 100644
--- a/src/api/zip.api.ts
+++ b/src/api/zip.api.ts
@@ -52,7 +52,7 @@ export const likeZip = async (bookstoreId: number) => {
// 서점 상세 정보
export const getZipDetail = async (bookstoreId: number, type: string, sortFiled: string) => {
try {
- const response = await instance.get(`api/bookstores/${bookstoreId}/details?type=${type}&sortFiled?=${sortFiled}`);
+ const response = await instance.get(`api/bookstores/${bookstoreId}/details?type=${type}&sortField=${sortFiled}`);
if (response.status == 200) {
return response.data;
}
diff --git a/src/components/Header/BookSearchHeader.tsx b/src/components/Header/BookSearchHeader.tsx
index ab55acf..c289135 100644
--- a/src/components/Header/BookSearchHeader.tsx
+++ b/src/components/Header/BookSearchHeader.tsx
@@ -3,12 +3,16 @@ import Arrow from '../../../public/icons/menu-bar/ArrowLeft.svg?react';
import { useNavigate } from 'react-router-dom';
import SearchBar from '../Zip/SearchBar';
+import { postSearchHistory } from '../../api/booksnap.api';
const BookSearchHeader = () => {
const [searchWord, setSearchWord] = useState('');
const nav = useNavigate();
- const handleSearch = () => {};
+ const handleSearch = () => {
+ nav(`/booksnap?query=${searchWord}`);
+ postSearchHistory('booktitle', searchWord);
+ };
return (
diff --git a/src/components/ScrollContext.tsx b/src/components/ScrollContext.tsx
new file mode 100644
index 0000000..0518af4
--- /dev/null
+++ b/src/components/ScrollContext.tsx
@@ -0,0 +1,11 @@
+import { createContext, useContext, useRef } from 'react';
+
+export const ScrollContext = createContext
| null>(null);
+
+export const useScrollRef = () => {
+ const context = useContext(ScrollContext);
+ if (!context) {
+ throw new Error('useScrollRef must be used within a ScrollProvider');
+ }
+ return context;
+};
diff --git a/src/components/layout.tsx b/src/components/layout.tsx
index db925a9..656a38d 100644
--- a/src/components/layout.tsx
+++ b/src/components/layout.tsx
@@ -1,9 +1,10 @@
import { Outlet } from 'react-router-dom';
import MenuBar from './Common/MenuBar';
import { useEffect, useRef, useState } from 'react';
+import { ScrollContext } from './ScrollContext';
const Layout = () => {
- const headerHeight = useRef(null);
+ const mainRef = useRef(null);
const menuBarHeight = useRef(null);
const [heights, setHeights] = useState(0);
@@ -24,21 +25,23 @@ const Layout = () => {
}, []);
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
);
};
diff --git a/src/pages/Bookie.tsx b/src/pages/Bookie.tsx
index 1d39f27..1240a88 100644
--- a/src/pages/Bookie.tsx
+++ b/src/pages/Bookie.tsx
@@ -30,7 +30,7 @@ const Bookie = () => {
const [isComposing, setIsComposing] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [loadingMessage, setLoadingMessage] = useState('');
- const userName = '이구역독서짱';
+ const userName = localStorage.getItem('nickname');
const [systemRes, setSystemRes] = useState([
{
text: `안녕하세요! ${userName}님이 좋아하실만한책을 추천해드리는 Bookie입니다! 더 많은 정보를 알려주시면, 책을 찾아드릴게요.`,
diff --git a/src/pages/Booksnap/BookSearch.tsx b/src/pages/Booksnap/BookSearch.tsx
index 6e32deb..7b2b6e3 100644
--- a/src/pages/Booksnap/BookSearch.tsx
+++ b/src/pages/Booksnap/BookSearch.tsx
@@ -1,23 +1,29 @@
+import { useEffect, useState } from 'react';
+import { getRecentSearch } from '../../api/booksnap.api';
import RecentSearch from '../../components/Booksnap/RecentSearch';
import Tag from '../../components/Common/Tag';
-import SearchBar from '../../components/Zip/SearchBar';
-import { useState } from 'react';
-import Arrow from '../../../public/icons/menu-bar/ArrowLeft.svg?react';
-import { useNavigate } from 'react-router-dom';
-import Header from '../../components/Common/Header';
import BookSearchHeader from '../../components/Header/BookSearchHeader';
-const BookSearch = () => {
- const nav = useNavigate();
+interface RecentType {
+ id: number;
+ searchWord: string;
+}
+const BookSearch = () => {
const bookname = ['구원의 날', '지구에서 한아뿐', '사이키쿠스오'];
- const recent = ['하이큐 10권', '우리가 빛의 속도로 갈 수 없다면', '천 개의 파랑'];
+ const [recent, setRecent] = useState([]);
+
+ useEffect(() => {
+ getRecentSearch('booktitle', 1, 10).then((data) => {
+ setRecent(data.data.searchHistory);
+ });
+ }, []);
return (
-
+
{/* 헤더 */}
-
+
{/* 인기 검색어 */}
zipzip이들이 많이 찾아본 리뷰
@@ -32,7 +38,7 @@ const BookSearch = () => {
최근 검색
{recent.map((book, index) => (
-
+
))}
diff --git a/src/pages/Booksnap/BookSnap.tsx b/src/pages/Booksnap/BookSnap.tsx
index de11e1e..5e40d4d 100644
--- a/src/pages/Booksnap/BookSnap.tsx
+++ b/src/pages/Booksnap/BookSnap.tsx
@@ -4,9 +4,11 @@ import ReviewPreview from '../../components/Booksnap/ReviewPreview';
import { BooksnapPreview } from '../../model/booksnap.model';
import Loading from '../Loading';
import WriteButton from '../../components/Booksnap/WriteButton';
-import { getReview } from '../../api/booksnap.api';
+import { getReview, searchReview } from '../../api/booksnap.api';
import Toast from '../../components/Common/Toast';
import BooksnapHeader from '../../components/Header/BooksnapHeader';
+import { useScrollRef } from '../../components/ScrollContext';
+import { useSearchParams } from 'react-router-dom';
const BookSnap = () => {
const [filter, setFilter] = useState
('createdAt');
@@ -14,9 +16,19 @@ const BookSnap = () => {
const [page, setPage] = useState(1);
const [isLast, setIsLast] = useState(false);
const [isBottom, setIsBottom] = useState(false);
- const mainRef = useRef(null);
const isLastRef = useRef(false);
const [isLoading, setIsLoading] = useState(false);
+ const mainRef = useScrollRef();
+ const [searchParams] = useSearchParams();
+ const query = searchParams.get('query');
+
+ useEffect(() => {
+ if (query) {
+ searchReview(query).then((data) => {
+ setReview(data.booksnapPreview);
+ });
+ }
+ }, [query]);
// 리뷰 목록 받아오기
const getReviews = async () => {
@@ -35,6 +47,7 @@ const BookSnap = () => {
// filter가 변경될 때 상태 초기화 및 getReviews 호출
useEffect(() => {
+ if (query) return;
setReview([]);
setIsLast(false);
setIsBottom(false);
@@ -48,6 +61,8 @@ const BookSnap = () => {
// page가 변경될 때만 getReviews 호출
useEffect(() => {
+ if (query) return;
+
if (page !== 1 || review.length === 0) {
// 🔄 리뷰가 없거나 페이지가 1이 아닐 때만 호출
getReviews();
@@ -59,32 +74,26 @@ const BookSnap = () => {
isLastRef.current = isLast;
}, [isLast]);
- // 바닥 감지
- const detectBottom = () => {
- if (mainRef.current) {
+ useEffect(() => {
+ const handleScroll = () => {
+ if (!mainRef.current) return;
+
const { scrollTop, clientHeight, scrollHeight } = mainRef.current;
- return scrollHeight > clientHeight && scrollTop + clientHeight >= scrollHeight - 1;
- }
- return false;
- };
+ if (scrollTop + clientHeight >= scrollHeight - 100 && !isBottom && !isLastRef.current) {
+ setIsBottom(true);
+ setPage((prev) => prev + 1);
+ }
+ };
- // 스크롤이 바닥에 있고, 마지막 페이지가 아니면 page + 1
- const handleScrollEvent = () => {
- if (detectBottom() && !isBottom && !isLastRef.current) {
- setIsBottom(true);
- setPage((prev) => prev + 1);
+ const el = mainRef.current;
+ if (el) {
+ el.addEventListener('scroll', handleScroll);
}
- };
- // 스크롤 이벤트 등록
- useEffect(() => {
- if (mainRef.current) {
- mainRef.current.addEventListener('scroll', handleScrollEvent);
- return () => {
- mainRef.current?.removeEventListener('scroll', handleScrollEvent);
- };
- }
- }, []);
+ return () => {
+ el?.removeEventListener('scroll', handleScroll);
+ };
+ }, [mainRef, isBottom]);
if (isLoading) {
return ;
@@ -95,7 +104,7 @@ const BookSnap = () => {
}
return (
-