Skip to content
33 changes: 33 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<style>
body {
overscroll-behavior: none;
touch-action: pan-y pinch-zoom;
}

* {
overscroll-behavior-x: none;
}
</style>
<script>
// 좌우 스와이프 네비게이션 방지
let startX = null;

document.addEventListener('touchstart', function(e) {
startX = e.touches[0].clientX;
}, { passive: true });

document.addEventListener('touchmove', function(e) {
if (!startX) return;

let currentX = e.touches[0].clientX;
let diffX = Math.abs(currentX - startX);
let diffY = Math.abs(e.touches[0].clientY - (e.touches[0].clientY || 0));

// 수평 스와이프가 수직보다 크면 preventDefault
if (diffX > diffY && diffX > 10) {
if ((currentX > startX && startX < 50) || (currentX < startX && startX > window.innerWidth - 50)) {
e.preventDefault();
}
}
}, { passive: false });
</script>
<script>
(function (m, a, z, e) {
var s, t;
Expand Down
21 changes: 16 additions & 5 deletions src/api/books/getSavedBooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export interface SavedBook {
// API 응답 데이터 타입
export interface SavedBooksData {
bookList: SavedBook[];
nextCursor: string;
isLast: boolean;
}

// API 응답 타입
Expand All @@ -23,13 +25,22 @@ export interface SavedBooksResponse {
data: SavedBooksData;
}

// 저장한 책 또는 참여 중 모임의 책 조회
export const getSavedBooks = async (type: 'saved' | 'joining'): Promise<SavedBooksResponse> => {
// 저장한 책 또는 참여 중 모임의 책 조회 (커서 기반 무한 스크롤)
export const getSavedBooks = async (
type: 'saved' | 'joining',
cursor: string | null = null
): Promise<SavedBooksResponse> => {
try {
const params: { type: string; cursor?: string } = {
type: type.toUpperCase(),
};

if (cursor !== null) {
params.cursor = cursor;
}

const response = await apiClient.get<SavedBooksResponse>('/books/selectable-list', {
params: {
type: type.toUpperCase(),
},
params,
});
return response.data;
} catch (error) {
Expand Down
15 changes: 12 additions & 3 deletions src/api/books/getSavedBooksInMy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export interface SavedBookInMy {
// API 응답 데이터 타입
export interface SavedBooksInMyData {
bookList: SavedBookInMy[];
nextCursor: string;
isLast: boolean;
}

// API 응답 타입
Expand All @@ -24,10 +26,17 @@ export interface SavedBooksInMyResponse {
data: SavedBooksInMyData;
}

// 개인적으로 저장한 책 목록 조회 API 함수
export const getSavedBooksInMy = async () => {
// 개인적으로 저장한 책 목록 조회 API 함수 (무한스크롤)
export const getSavedBooksInMy = async (cursor: string | null = null) => {
try {
const response = await apiClient.get<SavedBooksInMyResponse>('/books/saved');
const params: { cursor?: string | null } = {};
if (cursor !== null) {
params.cursor = cursor;
}

const response = await apiClient.get<SavedBooksInMyResponse>('/books/saved', {
params,
});
return response.data;
} catch (error) {
console.error('개인 저장 책 조회 API 오류:', error);
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/BookSearchBottomSheet/BookList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const BookList = ({
if (observerRef.current) observerRef.current.disconnect();

observerRef.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasNextPage && onLoadMore && isSearchMode) {
if (entries[0].isIntersecting && hasNextPage && onLoadMore) {
onLoadMore();
}
});
Expand Down Expand Up @@ -81,8 +81,8 @@ const BookList = ({
</BookItem>
))}

{/* 검색 모드에서 더 많은 데이터가 있고 로딩 중일 때 로딩 표시 */}
{isSearchMode && isLoadingMore && (
{/* 더 많은 데이터가 있고 로딩 중일 때 로딩 표시 */}
{isLoadingMore && (
<LoadingContainer ref={loadingRef}>
<LoadingText>더 많은 책을 불러오는 중...</LoadingText>
</LoadingContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ const BookSearchBottomSheet = ({ isOpen, onClose, onSelectBook }: BookSearchBott
showTabs,
hasNextPage,
isLoadingMore,
currentTabHasNext,
currentTabIsLoadingMore,
setSearchQuery,
handleTabChange,
loadInitialData,
performSearch,
loadMoreSearchResults,
loadMoreSavedBooks,
loadMoreGroupBooks,
} = useBookSearch();

// 컴포넌트가 열릴 때 초기 데이터 로드
Expand Down Expand Up @@ -78,6 +82,19 @@ const BookSearchBottomSheet = ({ isOpen, onClose, onSelectBook }: BookSearchBott
};

const showBookList = !isLoading && !error && !showEmptyState;
const isSearchMode = searchQuery.trim() !== '';

// 현재 탭에 맞는 무한 스크롤 핸들러 결정
const getLoadMoreHandler = () => {
if (isSearchMode) {
return loadMoreSearchResults;
}
return activeTab === 'saved' ? loadMoreSavedBooks : loadMoreGroupBooks;
};

// 현재 상태에 맞는 무한 스크롤 정보
const currentHasNextPage = isSearchMode ? hasNextPage : currentTabHasNext;
const currentIsLoadingMore = isSearchMode ? isLoadingMore : currentTabIsLoadingMore;

return (
<Overlay isVisible={isOpen} onClick={handleOverlayClick}>
Expand Down Expand Up @@ -108,10 +125,10 @@ const BookSearchBottomSheet = ({ isOpen, onClose, onSelectBook }: BookSearchBott
<BookList
books={filteredBooks}
onBookSelect={handleBookSelect}
onLoadMore={loadMoreSearchResults}
hasNextPage={hasNextPage}
isLoadingMore={isLoadingMore}
isSearchMode={searchQuery.trim() !== ''}
onLoadMore={getLoadMoreHandler()}
hasNextPage={currentHasNextPage}
isLoadingMore={currentIsLoadingMore}
isSearchMode={isSearchMode}
/>
)}
</BookListContainer>
Expand Down
90 changes: 78 additions & 12 deletions src/components/common/BookSearchBottomSheet/useBookSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ export const useBookSearch = () => {
const [currentPage, setCurrentPage] = useState(1);
const [hasNextPage, setHasNextPage] = useState(false);
const [isLoadingMore, setIsLoadingMore] = useState(false);

// 저장한 책/모임 책 무한 스크롤 관련 상태
const [savedBooksCursor, setSavedBooksCursor] = useState<string | null>(null);
const [groupBooksCursor, setGroupBooksCursor] = useState<string | null>(null);
const [hasSavedBooksNext, setHasSavedBooksNext] = useState(false);
const [hasGroupBooksNext, setHasGroupBooksNext] = useState(false);
const [isLoadingMoreSavedBooks, setIsLoadingMoreSavedBooks] = useState(false);
const [isLoadingMoreGroupBooks, setIsLoadingMoreGroupBooks] = useState(false);

// API에서 받은 데이터를 Book 타입으로 변환하는 함수
const convertSavedBookToBook = (savedBook: SavedBook): Book => ({
Expand All @@ -36,45 +44,77 @@ export const useBookSearch = () => {
isbn: searchedBook.isbn,
});

// 저장한 책 데이터 가져오기
const fetchSavedBooks = async () => {
// 저장한 책 데이터 가져오기 (무한 스크롤)
const fetchSavedBooks = async (isLoadMore: boolean = false) => {
try {
setIsLoading(true);
if (isLoadMore) {
setIsLoadingMoreSavedBooks(true);
} else {
setIsLoading(true);
setSavedBooksCursor(null);
}
setError(null);

const response = await getSavedBooks('saved');
const cursor = isLoadMore ? savedBooksCursor : null;
const response = await getSavedBooks('saved', cursor);

if (response.isSuccess && response.data) {
setSavedBooks(response.data.bookList);
if (isLoadMore) {
setSavedBooks(prev => [...prev, ...response.data.bookList]);
} else {
setSavedBooks(response.data.bookList);
}
setSavedBooksCursor(response.data.nextCursor);
setHasSavedBooksNext(!response.data.isLast);
} else {
setError(response.message || '저장한 책을 불러오는데 실패했습니다.');
}
} catch (err) {
console.error('저장한 책 조회 오류:', err);
setError('저장한 책을 불러오는데 실패했습니다.');
} finally {
setIsLoading(false);
if (isLoadMore) {
setIsLoadingMoreSavedBooks(false);
} else {
setIsLoading(false);
}
}
};

// 모임 책 데이터 가져오기
const fetchGroupBooks = async () => {
// 모임 책 데이터 가져오기 (무한 스크롤)
const fetchGroupBooks = async (isLoadMore: boolean = false) => {
try {
setIsLoading(true);
if (isLoadMore) {
setIsLoadingMoreGroupBooks(true);
} else {
setIsLoading(true);
setGroupBooksCursor(null);
}
setError(null);

const response = await getSavedBooks('joining');
const cursor = isLoadMore ? groupBooksCursor : null;
const response = await getSavedBooks('joining', cursor);

if (response.isSuccess && response.data) {
setGroupBooks(response.data.bookList);
if (isLoadMore) {
setGroupBooks(prev => [...prev, ...response.data.bookList]);
} else {
setGroupBooks(response.data.bookList);
}
setGroupBooksCursor(response.data.nextCursor);
setHasGroupBooksNext(!response.data.isLast);
} else {
setError(response.message || '모임 책을 불러오는데 실패했습니다.');
}
} catch (err) {
console.error('모임 책 조회 오류:', err);
setError('모임 책을 불러오는데 실패했습니다.');
} finally {
setIsLoading(false);
if (isLoadMore) {
setIsLoadingMoreGroupBooks(false);
} else {
setIsLoading(false);
}
}
};

Expand Down Expand Up @@ -140,6 +180,24 @@ export const useBookSearch = () => {
await performSearch(searchQuery.trim(), currentPage + 1, false);
};

// 더 많은 저장한 책 로드
const loadMoreSavedBooks = async () => {
if (isLoadingMoreSavedBooks || !hasSavedBooksNext) {
return;
}

await fetchSavedBooks(true);
};

// 더 많은 모임 책 로드
const loadMoreGroupBooks = async () => {
if (isLoadingMoreGroupBooks || !hasGroupBooksNext) {
return;
}

await fetchGroupBooks(true);
};

// 검색어 변경 핸들러 (디바운싱 적용)
const handleSearchQueryChange = (query: string) => {
setSearchQuery(query);
Expand Down Expand Up @@ -204,6 +262,10 @@ export const useBookSearch = () => {
const hasBooks = isSearchMode ? searchResults.length > 0 : currentTabBooks.length > 0;
const showEmptyState = !isLoading && !error && !hasBooks;
const showTabs = !isSearchMode; // 검색 모드가 아닐 때는 항상 탭 표시

// 현재 탭의 무한 스크롤 상태
const currentTabHasNext = activeTab === 'saved' ? hasSavedBooksNext : hasGroupBooksNext;
const currentTabIsLoadingMore = activeTab === 'saved' ? isLoadingMoreSavedBooks : isLoadingMoreGroupBooks;

// 컴포넌트 언마운트 시 타이머 정리
useEffect(() => {
Expand All @@ -226,12 +288,16 @@ export const useBookSearch = () => {
showTabs,
hasNextPage,
isLoadingMore,
currentTabHasNext,
currentTabIsLoadingMore,

// Actions
setSearchQuery: handleSearchQueryChange,
handleTabChange,
loadInitialData,
performSearch,
loadMoreSearchResults,
loadMoreSavedBooks,
loadMoreGroupBooks,
};
};
1 change: 1 addition & 0 deletions src/components/common/Post/PostBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const ImageContainer = styled.div`
width: 100px;
height: 100px;
flex-shrink: 0; //고정사이즈
object-fit: cover;
}
`;

Expand Down
14 changes: 7 additions & 7 deletions src/pages/mypage/Mypage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,17 @@ const UserProfile = styled.div`
gap: 4px;

.username {
color: var(--color-white);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
line-height: normal;
color: var(--color-text-primary_white, #fefefe);
font-size: var(--string-size-large01, 18px);
font-weight: var(--string-weight-semibold, 600);
line-height: var(--string-lineheight-height24, 24px); /* 133.333% */
letter-spacing: 0.018px;
}

.usertitle {
color: var(--color-text-humanities_skyblue, #a1d5ff);
font-size: var(--font-size-xs);
font-size: var(--string-size-medium01, 14px);
font-weight: var(--string-weight-regular, 400);
line-height: normal;
line-height: var(--string-lineheight-feedcontent_height20, 20px); /* 142.857% */
}
}
}
Expand Down
Loading