All files / src/components/pages/message/message-following-content index.tsx

68.36% Statements 67/98
20% Branches 1/5
33.33% Functions 1/3
68.36% Lines 67/98

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 991x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x             1x 1x 1x 1x 1x 1x 1x 1x         1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                           1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
'use client';
 
import { useSearchParams } from 'next/navigation';
 
import { API } from '@/api';
import { ChatList } from '@/components/pages/chat';
import { FollowingList, FollowingNone, FollowingSearch } from '@/components/pages/message';
import { TabNavigation } from '@/components/shared';
import { useInfiniteScroll } from '@/hooks/use-group/use-group-infinite-list';
import { useIntersectionObserver } from '@/hooks/use-intersection-observer';
import { INTERSECTION_OBSERVER_THRESHOLD } from '@/lib/constants/group-list';
 
const SOCIAL_TABS = [
  { label: '팔로잉', value: 'following' },
  { label: '메세지', value: 'chat' },
];
 
interface FollowingContentProps {
  initialUserId: number;
}
 
export const FollowingContent = ({ initialUserId }: FollowingContentProps) => {
  const params = useSearchParams();
  const tab = params.get('tab') || 'chat';
 
  // 1. 무한 스크롤 훅 호출 (서버에서 prefetch된 데이터가 자동으로 사용됨)
  const {
    items: followers,
    error,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
    completedMessage,
  } = useInfiniteScroll({
    queryFn: async ({ cursor, size }) => {
      return await API.followerService.getFollowers({
        userId: initialUserId,
        cursor,
        size,
      });
    },
    queryKey: ['followers', initialUserId],
    completedMessage: '모든 팔로잉을 불러왔습니다.',
    enabled: !!initialUserId,
  });
 
  // 2. IntersectionObserver로 스크롤 감지
  const sentinelRef = useIntersectionObserver({
    onIntersect: () => {
      if (hasNextPage && !isFetchingNextPage) {
        fetchNextPage();
      }
    },
    enabled: hasNextPage && error === null,
    threshold: INTERSECTION_OBSERVER_THRESHOLD,
  });
 
  return (
    <div className='min-h-screen bg-[#F1F5F9]'>
      <TabNavigation basePath='/message' defaultValue='chat' tabs={SOCIAL_TABS} />
 
      {tab === 'chat' && <ChatList />}
      {tab === 'following' && (
        <>
          <FollowingSearch userId={initialUserId} />
          {!error && followers && followers.length > 0 ? (
            <>
              <FollowingList items={followers} />

              {/* 3. Sentinel 요소 (필수!) */}
              {hasNextPage && <div ref={sentinelRef} className='h-1' />}

              {/* 4. 다음 페이지 로딩 중 */}
              {isFetchingNextPage && (
                <div className='flex items-center justify-center p-4'>
                  <span className='text-gray-500'>더 불러오는 중...</span>
                </div>
              )}

              {/* 5. 완료 메시지 */}
              {!hasNextPage && (
                <div className='flex items-center justify-center p-4'>
                  <span className='text-gray-500'>{completedMessage}</span>
                </div>
              )}
            </>
          ) : (
            !error && (
              <div className='flex flex-1 items-center justify-center'>
                <FollowingNone />
              </div>
            )
          )}
        </>
      )}
    </div>
  );
};