Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions src/api/booksnap.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,6 @@ export const searchReview = async (bookName: string) => {
}
};

// 책 검색 기록 저장
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 {
Expand Down
5 changes: 3 additions & 2 deletions src/components/Booksnap/RecentSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { IoTrashSharp } from 'react-icons/io5';

interface RecentSearchProps {
name: string;
onClick: (book: string) => void;
}

const RecentSearch = ({ name }: RecentSearchProps) => {
const RecentSearch = ({ name, onClick }: RecentSearchProps) => {
return (
<div className="flex justify-around gap-[10px] p-[10px]">
<div className="flex cursor-pointer justify-around gap-[10px] p-[10px]" onClick={() => onClick(name)}>
<IoMdTime className="h-6 w-6 fill-[#A09F9F]" />
<p className="flex-1 text-[#A09F9F]">{name}</p>
<IoTrashSharp className="h-5 w-5 fill-[#A09F9F]" />
Expand Down
23 changes: 15 additions & 8 deletions src/components/Header/BookSearchHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
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('');
interface BookSearchHeaderProps {
query?: string;
}

const BookSearchHeader = ({ query }: BookSearchHeaderProps) => {
const [searchWord, setSearchWord] = useState(query || '');
const nav = useNavigate();

useEffect(() => {
if (query !== undefined) {
setSearchWord(query);
}
}, [query]);

const handleSearch = () => {
nav(`/booksnap?query=${searchWord}`);
postSearchHistory('booktitle', searchWord);
};

return (
<div className="fixed left-0 right-0 top-0 z-20 m-auto flex max-w-[500px] items-center gap-[15px] border-b-[1px] border-[#544F4F] bg-bg px-[20px] py-[10px]">
<Arrow onClick={() => nav(-1)} />
<Arrow onClick={() => nav('/booksnap')} />
<SearchBar
searchWord={searchWord}
setSearchWord={setSearchWord}
onSearch={handleSearch}
text="책 제목으로 리뷰를 찾아보세요!"
></SearchBar>
/>
</div>
);
};
Expand Down
9 changes: 7 additions & 2 deletions src/pages/Booksnap/BookSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,22 @@ interface RecentType {
const BookSearch = () => {
const bookname = ['구원의 날', '지구에서 한아뿐', '사이키쿠스오'];
const [recent, setRecent] = useState<RecentType[]>([]);
const [word, setWord] = useState('');

useEffect(() => {
getRecentSearch('booktitle', 1, 10).then((data) => {
setRecent(data.data.searchHistory);
});
}, []);

const handleClick = (bookName: string) => {
setWord(bookName);
};

return (
<div className="flex h-full flex-col bg-bg">
{/* 헤더 */}
<BookSearchHeader />
<BookSearchHeader query={word} />
<div className="mt-[53px] flex flex-col gap-[15px] bg-bg px-[15px] py-[10px]">
{/* 인기 검색어 */}
<div className="flex w-full flex-col gap-[10px] border-b-[1px] border-[#A09F9F] p-[10px] text-body3 font-bold text-white">
Expand All @@ -38,7 +43,7 @@ const BookSearch = () => {
<p className="px-[10px] text-body3 font-bold text-white">최근 검색</p>
<div className="flex flex-col gap-[5px]">
{recent.map((book, index) => (
<RecentSearch name={book.searchWord} key={index} />
<RecentSearch name={book.searchWord} key={index} onClick={() => handleClick(book.searchWord)} />
))}
</div>
</div>
Expand Down
55 changes: 27 additions & 28 deletions src/pages/Booksnap/BookSnap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,49 @@ import Toast from '../../components/Common/Toast';
import BooksnapHeader from '../../components/Header/BooksnapHeader';
import { useScrollRef } from '../../components/ScrollContext';
import { useSearchParams } from 'react-router-dom';
import BookSearchHeader from '../../components/Header/BookSearchHeader';

const BookSnap = () => {
const [filter, setFilter] = useState<FilterType>('createdAt');
const [review, setReview] = useState<BooksnapPreview[]>([]);
const [page, setPage] = useState(1);
const [isLast, setIsLast] = useState<boolean>(false);
const [isBottom, setIsBottom] = useState<boolean>(false);
const isLastRef = useRef<boolean>(false);
const [isLast, setIsLast] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const isLastRef = useRef(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 () => {
setIsLoading(true);
try {
const data = await getReview(filter, page);
setReview((prev) => (page === 1 ? data.data.booksnapPreview : [...prev, ...data.data.booksnapPreview]));
setIsLast(data.data.last);
setIsBottom(false);
} catch (error) {
console.error('리뷰를 불러오는 중 에러 발생:', error);
} finally {
setIsLoading(false);
}
};

// filter가 변경될 때 상태 초기화 및 getReviews 호출
// 검색어 있을 때는 검색 API 호출
useEffect(() => {
setReview([]);
if (query) {
searchReview(query).then((data) => {
setReview(data.booksnapPreview);
});
} else {
getReviews();
}
}, [query]);

useEffect(() => {
if (query) return;
setReview([]);
setIsLast(false);
setIsBottom(false);

if (page !== 1) {
setPage(1); // page가 1이 아니면 1로 초기화 (getReviews는 page가 바뀔 때 호출됨)
Expand All @@ -59,28 +60,26 @@ const BookSnap = () => {
}
}, [filter]);

// page가 변경될 때만 getReviews 호출
// page가 바뀌면 getReviews 호출
useEffect(() => {
if (query) return;

if (page !== 1 || review.length === 0) {
// 🔄 리뷰가 없거나 페이지가 1이 아닐 때만 호출
getReviews();
}
getReviews();
}, [page]);

// isLast 업데이트 시 참조 업데이트
// isLast 상태를 ref에도 반영
useEffect(() => {
isLastRef.current = isLast;
}, [isLast]);

// 스크롤 이벤트 등록
useEffect(() => {
const handleScroll = () => {
if (!mainRef.current) return;

const { scrollTop, clientHeight, scrollHeight } = mainRef.current;
if (scrollTop + clientHeight >= scrollHeight - 100 && !isBottom && !isLastRef.current) {
setIsBottom(true);
const nearBottom = scrollTop + clientHeight >= scrollHeight - 100;

if (nearBottom && !isLastRef.current && !isLoading) {
setPage((prev) => prev + 1);
}
};
Expand All @@ -93,9 +92,9 @@ const BookSnap = () => {
return () => {
el?.removeEventListener('scroll', handleScroll);
};
}, [mainRef, isBottom]);
}, [mainRef, isLoading]);

if (isLoading) {
if (isLoading && review.length === 0) {
return <Loading text="리뷰 목록을 불러오는 중입니다!" />;
}

Expand All @@ -106,9 +105,9 @@ const BookSnap = () => {
return (
<div className="overflow-hidden bg-bg scrollbar-none">
{/* 헤더 */}
<BooksnapHeader />
<div className="mt-[50px] flex flex-col">
<FilterBar filter={filter} setFilter={setFilter} />
{query ? <BookSearchHeader query={query} /> : <BooksnapHeader />}
<div className={`flex flex-col ${query ? '' : 'mt-[50px]'}`}>
{!query && <FilterBar filter={filter} setFilter={setFilter} />}
<div className="mt-8 flex flex-col gap-6 px-8 py-8">
{review.map((preview, index) => (
<ReviewPreview review={preview} key={index} />
Expand Down