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: 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
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
75 changes: 69 additions & 6 deletions src/pages/mypage/SavePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,41 @@ const SavePage = () => {

// 책 관련 상태
const [savedBooks, setSavedBooks] = useState<SavedBookInMy[]>([]);
const [bookNextCursor, setBookNextCursor] = useState<string | null>(null);
const [bookIsLast, setBookIsLast] = useState(false);
const [bookLoading, setBookLoading] = useState(false);

// 초기 로딩 상태
const [initialLoading, setInitialLoading] = useState(true);

// Intersection Observer ref
const feedObserverRef = useRef<HTMLDivElement>(null);
const bookObserverRef = useRef<HTMLDivElement>(null);

const handleBack = () => {
navigate('/mypage');
};

// 저장된 책 목록 로드 함수
const loadSavedBooks = useCallback(async () => {
// 저장된 책 목록 로드 함수 (무한스크롤)
const loadSavedBooks = useCallback(async (cursor: string | null = null) => {
try {
const response = await getSavedBooksInMy();
setSavedBooks(response.data.bookList);
setBookLoading(true);
const response = await getSavedBooksInMy(cursor);

if (cursor === null) {
// 첫 로드
setSavedBooks(response.data.bookList);
} else {
// 추가 로드
setSavedBooks(prev => [...prev, ...response.data.bookList]);
}

setBookNextCursor(response.data.nextCursor);
setBookIsLast(response.data.isLast);
} catch (error) {
console.error('저장된 책 목록 로드 실패:', error);
} finally {
setBookLoading(false);
}
}, []);

Expand All @@ -72,6 +89,35 @@ const SavePage = () => {
}
}, []);

// 저장된 책 목록 로드 함수
const loadMoreBooks = useCallback(async () => {
if (!bookNextCursor || bookIsLast || bookLoading) return;

try {
await loadSavedBooks(bookNextCursor);
} catch (error) {
console.error('책 추가 로드 실패:', error);
}
}, [bookNextCursor, bookIsLast, bookLoading, loadSavedBooks]);

// 책 Intersection Observer 콜백
const lastBookElementCallback = useCallback(
(node: HTMLDivElement | null) => {
if (bookLoading || bookIsLast) return;

if (node) {
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && !bookLoading && !bookIsLast) {
loadMoreBooks();
}
});

observer.observe(node);
}
},
[bookLoading, bookIsLast, loadMoreBooks],
);

// 페이지 진입 시 모든 데이터 로드 (한 번만 실행)
useEffect(() => {
const loadAllData = async () => {
Expand All @@ -91,6 +137,8 @@ const SavePage = () => {

// 책 데이터 설정
setSavedBooks(booksResponse.data.bookList);
setBookNextCursor(booksResponse.data.nextCursor);
setBookIsLast(booksResponse.data.isLast);
} catch (error) {
console.error('초기 데이터 로드 실패:', error);
} finally {
Expand Down Expand Up @@ -203,8 +251,11 @@ const SavePage = () => {
) : savedBooks.length > 0 ? (
<>
<BookList>
{savedBooks.map(book => (
<BookItem key={book.bookId}>
{savedBooks.map((book, index) => (
<BookItem
key={book.bookId}
ref={index === savedBooks.length - 1 ? lastBookElementCallback : null}
>
<LeftSection>
<Cover src={book.bookImageUrl} alt={`${book.bookTitle} 커버`} />
<BookInfo>
Expand All @@ -222,6 +273,12 @@ const SavePage = () => {
</SaveIcon>
</BookItem>
))}
{/* 무한스크롤을 위한 observer 요소 */}
{!bookIsLast && (
<div ref={bookObserverRef} style={{ height: '20px' }}>
{bookLoading && <LoadingSpinner fullHeight={false} size="small" />}
</div>
)}
</BookList>
</>
) : (
Expand Down Expand Up @@ -297,9 +354,15 @@ const BookList = styled.div`
`;

const BookItem = styled.div`
width: 94.8%;
margin: 0 auto;
display: flex;
border-bottom: 1px solid ${colors.darkgrey.dark};
padding: 12px 20px;

&:last-child {
border-bottom: none;
}
justify-content: space-between;
`;

Expand Down