All files / src/components/pages/user/profile/profile-follows-modal index.tsx

27.65% Statements 26/94
100% Branches 0/0
0% Functions 0/4
27.65% Lines 26/94

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 951x 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                                                                        
import Link from 'next/link';
 
import { Suspense } from 'react';
 
import { UseSuspenseInfiniteQueryResult } from '@tanstack/react-query';
 
import { ProfileImage } from '@/components/shared';
import { ModalContent, ModalDescription, ModalTitle, useModal } from '@/components/ui';
import { useGetFolloweesInfinite, useGetFollowersInfinite } from '@/hooks/use-follower';
import { useIntersectionObserver } from '@/hooks/use-intersection-observer';
import { CommonErrorResponse } from '@/types/service/common';
import { FollowItem, FollowType } from '@/types/service/follow';
import { User } from '@/types/service/user';
 
interface Props {
  user: User;
  type: FollowType;
}
 
export const ProfileFollowsModal = ({ user, type }: Props) => {
  const title: Record<FollowType, string> = {
    followers: '팔로워',
    followees: '팔로잉',
  };

  const followsCount: Record<FollowType, number> = {
    followers: user.followersCnt,
    followees: user.followeesCnt,
  };

  return (
    <ModalContent className='max-w-90'>
      <ModalTitle className='flex flex-row gap-1'>
        <span>{title[type]}</span>
        <span className='text-mint-500'>{followsCount[type]}</span>
      </ModalTitle>
      <ModalDescription className='sr-only'>
        {`${title[type]} 목록을 확인할 수 있는 모달입니다.`}
      </ModalDescription>
      {/* todo: suspense fallback 디자인 필요 */}
      <Suspense fallback={<span>로딩중...</span>}>
        {type === 'followers' && <Followers user={user} />}
        {type === 'followees' && <Followees user={user} />}
      </Suspense>
    </ModalContent>
  );
};
 
const Followers = ({ user }: { user: User }) => {
  const query = useGetFollowersInfinite({ userId: user.userId });
  return <FollowList query={query} />;
};
 
const Followees = ({ user }: { user: User }) => {
  const query = useGetFolloweesInfinite({ userId: user.userId });
  return <FollowList query={query} />;
};
 
const FollowList = ({
  query,
}: {
  query: UseSuspenseInfiniteQueryResult<FollowItem[], CommonErrorResponse>;
}) => {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = query;
  const { close } = useModal();

  const fetchObserverRef = useIntersectionObserver({
    onIntersect: () => {
      if (hasNextPage && !isFetchingNextPage) {
        fetchNextPage();
      }
    },
  });

  return (
    <div className='scrollbar-thin mt-4 flex h-96 flex-col overflow-y-scroll'>
      {data?.map((item) => (
        <Link
          key={item.userId}
          href={`/profile/${item.userId}`}
          className='flow-row flex gap-4 py-2'
          onClick={close}
        >
          <ProfileImage size='md' src={item.profileImage} />
          <div>
            <p className='text-text-md-bold text-gray-800'>{item.nickname}</p>
            <p className='text-text-sm-medium text-gray-600'>{item.profileMessage}</p>
          </div>
        </Link>
      ))}
      {hasNextPage && <div ref={fetchObserverRef}></div>}
    </div>
  );
};