Skip to content

Commit

Permalink
Merge pull request #74 from Findy-org/feat/#69-pagination
Browse files Browse the repository at this point in the history
[FEAT] cursor 방식 페이지네이션 구현
  • Loading branch information
keemsebin authored Nov 26, 2024
2 parents db3c3bf + 777ecef commit 45c8018
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 92 deletions.
4 changes: 2 additions & 2 deletions src/components/common/Button/Button.variants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export const ButtonVariants = cva(
gray: 'bg-gray-150 text-gray-950 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed',
},
size: {
large: 'w-full h-16 text-xl font-medium',
medium: 'w-[19rem] h-14 text-lg font-medium',
large: 'w-full h-14 text-xl font-medium',
medium: 'w-[19rem] h-12 text-lg font-medium',
},
},
defaultVariants: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const Content = ({ children }: PropsWithChildren) => {
initial={FADE_IN_ANIMATION.initial}
animate={FADE_IN_ANIMATION.animate}
exit={FADE_IN_ANIMATION.exit}
className="relative z-modal bg-white px-6 py-7 rounded-[1.25rem] shadow-lg"
className="relative z-modal bg-white px-4 py-4 rounded-[1.25rem] shadow-lg"
>
{children}
</motion.div>
Expand Down
65 changes: 50 additions & 15 deletions src/components/features/BookmarkDetail/BookmarkDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useAtom } from 'jotai';

import { Button } from '@/components/common/Button';
import { Chip } from '@/components/common/Chip';
import { Icon } from '@/components/common/Icon';
import { ListCard } from '@/components/common/ListCard';
import { Body1, Body2, Body4 } from '@/components/common/Typography';
import { Delete } from '@/components/features/DeleteModal';
import { markersAtom } from '@/contexts/MarkerAtom';
import { useDeleteMarkers } from '@/hooks/api/marker/useDeleteMarkers';
import { useMarkerList } from '@/hooks/api/marker/useMarkerList';
import { useAuth } from '@/hooks/auth/useAuth';
import { Category } from '@/types/naver';

import { Delete } from '../DeleteModal';

type Props = { bookmarkId: number; onPrev: () => void };
export const BookmarkDetail = ({ bookmarkId, onPrev }: Props) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [isEditing, setIsEditing] = useState<boolean>(false);
const [selectedId, setSelectedId] = useState<number>(0);
const [, setMarkers] = useAtom(markersAtom);
const observerTarget = useRef<HTMLDivElement>(null);

const { token } = useAuth();
const { data } = useMarkerList(bookmarkId, token);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useMarkerList(bookmarkId, token);
const deleteMarkersMutation = useDeleteMarkers();
const [, setMarkers] = useAtom(markersAtom);

const handleToggleSelect = (markerId: number) => {
setSelectedId((prev) => (prev === markerId ? 0 : markerId));
Expand All @@ -40,14 +40,49 @@ export const BookmarkDetail = ({ bookmarkId, onPrev }: Props) => {
setSelectedId(0);
};

const allMarkers = useMemo(
() => (data?.pages ? data.pages.flatMap((page) => page.markers.data) : []),
[data?.pages]
);

useEffect(() => {
if (data?.markers?.data) {
setMarkers(data.markers.data);
if (allMarkers.length) {
setMarkers((prevMarkers) => {
const newMarkersStr = JSON.stringify(allMarkers);
const prevMarkersStr = JSON.stringify(prevMarkers);
return newMarkersStr !== prevMarkersStr ? allMarkers : prevMarkers;
});
}
}, [data?.markers?.data, setMarkers]);
}, [allMarkers, setMarkers]);

const selectedMarkerName =
data?.markers.data.find((item) => item.markerId === selectedId)?.title || '마커';
const selectedMarkerName = useMemo(
() => allMarkers.find((item) => item.markerId === selectedId)?.title || '마커',
[allMarkers, selectedId]
);

const handleObserver = useCallback(
(entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
});
},
[fetchNextPage, hasNextPage, isFetchingNextPage]
);

useEffect(() => {
const observer = new IntersectionObserver(handleObserver, {
root: null,
rootMargin: '20px',
threshold: 0.1,
});

if (observerTarget.current) {
observer.observe(observerTarget.current);
}
return () => observer.disconnect();
}, [handleObserver]);

return (
<div>
Expand All @@ -62,15 +97,15 @@ export const BookmarkDetail = ({ bookmarkId, onPrev }: Props) => {
className="cursor-pointer"
/>
<Body1 className="my-4 mx-3" weight="semibold" onClick={onPrev}>
<span className="text-primary mr-2">{data?.bookmarkName}</span>
<span className="text-primary mr-2">{data?.pages[0]?.bookmarkName}</span>
리스트
</Body1>
</div>
<ListCard>
{data?.markers.data.map((item, index) => (
{allMarkers.map((item, index) => (
<div key={item.markerId}>
<div
className={`flex flex-row justify-between gap-4 items-center ${index !== data.markers.data.length - 1 && 'pb-2'}`}
className={`flex flex-row justify-between gap-4 items-center ${index !== allMarkers.length - 1 && 'pb-2'} `}
>
<div className="flex flex-col gap-1 py-2">
<div className="flex flex-row gap-3 items-center">
Expand All @@ -96,10 +131,10 @@ export const BookmarkDetail = ({ bookmarkId, onPrev }: Props) => {
/>
)}
</div>

{index < data.markers.data.length - 1 && <hr className="border-dashed pt-2" />}
{index < allMarkers.length - 1 && <hr className="border-dashed pt-2" />}
</div>
))}
<div ref={observerTarget} />
</ListCard>
<div className="flex gap-4 mt-5">
{isEditing ? (
Expand Down
102 changes: 67 additions & 35 deletions src/components/features/BookmarkList/BookmarkList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';

import { Button } from '@/components/common/Button';
import { Icon } from '@/components/common/Icon';
Expand All @@ -22,9 +22,11 @@ export const BookmarkList = ({ onNext }: Props) => {
const [isEditing, setIsEditing] = useState<boolean>(false);
const [selectedId, setSelectedId] = useState<number>(0);
const [bookmarkName, setBookmarkName] = useState<string>('');
const observerTarget = useRef<HTMLDivElement>(null);

const { token } = useAuth();
const { data } = useBookMarkList(token);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useBookMarkList(token);

const deleteBookmarkMutation = useDeleteBookmark();
const newBookmarkMutation = useNewBookMark(token);

Expand Down Expand Up @@ -63,7 +65,32 @@ export const BookmarkList = ({ onNext }: Props) => {
};

const selectedItemName =
data?.data.find((item) => item.bookmarkId === selectedId)?.name || '북마크';
data?.pages.flatMap((page) => page.data).find((item) => item.bookmarkId === selectedId)?.name ||
'북마크';

const handleObserver = useCallback(
(entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
});
},
[fetchNextPage, hasNextPage, isFetchingNextPage]
);

useEffect(() => {
const observer = new IntersectionObserver(handleObserver, {
root: null,
rootMargin: '20px',
threshold: 0.1,
});

if (observerTarget.current) {
observer.observe(observerTarget.current);
}
return () => observer.disconnect();
}, [handleObserver]);

return (
<>
Expand All @@ -81,44 +108,49 @@ export const BookmarkList = ({ onNext }: Props) => {
</Body2>
</div>
<hr className="border-dashed pt-2" />
{data?.data.map((item, index) => (
<div key={item.bookmarkId}>
<div
onClick={() => handleBookmarkClick(item.bookmarkId)}
className="flex flex-row justify-between items-center cursor-pointer"
>
<div className="flex flex-row gap-4 py-2.5 items-center justify-center">
{item.youtuberProfile ? (
<img
src={item.youtuberProfile}
className="w-12 h-12 rounded-full"
alt={`${item.name}의 프로필 이미지`}
/>
) : (
{data?.pages
.flatMap((page) => page.data)
.map((item, index) => (
<div key={item.bookmarkId}>
<div
onClick={() => handleBookmarkClick(item.bookmarkId)}
className="flex flex-row justify-between items-center cursor-pointer"
>
<div className="flex flex-row gap-4 py-2.5 items-center justify-center">
{item.youtuberProfile ? (
<img
src={item.youtuberProfile}
className="w-12 h-12 rounded-full"
alt={`${item.name}의 프로필 이미지`}
/>
) : (
<Icon
name={findyIconNames[index % findyIconNames.length]}
className="w-11 h-11"
/>
)}
<div className="flex flex-col py-1">
<Body2 weight="medium">{item.name}</Body2>
<div className="flex flex-row items-center gap-1">
<Icon name="location" size={15} />
<Body3 className=" text-gray-500">{item.markersCount}</Body3>
</div>
</div>
</div>
{isEditing && (
<Icon
name={findyIconNames[index % findyIconNames.length]}
className="w-11 h-11"
name="check"
className="cursor-pointer h-7 w-7 flex-shrink-0"
color={selectedId === item.bookmarkId ? 'primary' : 'gray'}
/>
)}
<div className="flex flex-col py-1">
<Body2 weight="medium">{item.name}</Body2>
<div className="flex flex-row items-center gap-1">
<Icon name="location" size={15} />
<Body3 className=" text-gray-500">{item.markersCount}</Body3>
</div>
</div>
</div>
{isEditing && (
<Icon
name="check"
className="cursor-pointer h-7 w-7 flex-shrink-0"
color={selectedId === item.bookmarkId ? 'primary' : 'gray'}
/>
{index < data.pages.flatMap((page) => page.data).length - 1 && (
<hr className="border-dashed pt-2" />
)}
</div>
{index < data.data.length - 1 && <hr className="border-dashed pt-2" />}
</div>
))}
))}
<div ref={observerTarget} />
</ListCard>
<div className="flex gap-4 mt-5">
{isEditing ? (
Expand Down
Loading

0 comments on commit 45c8018

Please sign in to comment.