All files / src/components/pages/pending/pending-members index.tsx

100% Statements 123/123
100% Branches 28/28
100% Functions 8/8
100% Lines 123/123

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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 1241x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 1x 1x 10x 10x 10x 10x 1x 10x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 10x 1x 10x 1x 1x 1x 1x 1x 1x 10x 10x 10x 10x 1x 1x 10x 10x 10x 10x 10x 1x 1x 10x 10x 10x 10x 1x 1x 9x 10x 1x 1x 8x 10x 1x 1x 1x 1x 1x 1x 7x 10x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x  
'use client';
 
import { useRouter } from 'next/navigation';
 
import { useCallback, useEffect, useMemo } from 'react';
 
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 
import { API } from '@/api';
import { EmptyState } from '@/components/layout/empty-state';
import { Toast } from '@/components/ui';
import { useToast } from '@/components/ui/toast/core';
import { groupKeys } from '@/lib/query-key/query-key-group';
import { GetJoinRequestsResponse } from '@/types/service/group';
 
import { PENDING_MEMBERS_MIN_HEIGHT } from './constants';
import { PendingMemberCard } from './pending-member-card';
import { PendingMembersSkeleton } from './pending-members-loading';
 
interface Props {
  groupId: string;
}
 
export const GroupPendingMembers = ({ groupId }: Props) => {
  const router = useRouter();
  const queryClient = useQueryClient();
  const { run } = useToast();
 
  const { data, isLoading, error } = useQuery<GetJoinRequestsResponse>({
    queryKey: groupKeys.joinRequests(groupId, 'PENDING'),
    queryFn: () => API.groupService.getJoinRequests({ groupId }, 'PENDING'),
  });
 
  const isForbidden = useMemo(
    () => error && typeof error === 'object' && 'status' in error && error.status === 403,
    [error],
  );
 
  useEffect(() => {
    if (isForbidden) {
      router.replace('/');
    }
  }, [isForbidden, router]);
 
  const approveMutation = useMutation({
    mutationFn: (targetUserId: string) =>
      API.groupService.approveJoinRequest({ groupId, targetUserId }),
    onSuccess: async () => {
      await queryClient.invalidateQueries({
        queryKey: groupKeys.joinRequests(groupId, 'PENDING'),
        refetchType: 'active',
      });
      await queryClient.invalidateQueries({ queryKey: groupKeys.detail(groupId) });
      run(<Toast type='success'>모임 신청이 수락되었습니다.</Toast>);
    },
  });
 
  const rejectMutation = useMutation({
    mutationFn: (targetUserId: string) =>
      API.groupService.rejectJoinRequest({ groupId, targetUserId }),
    onSuccess: async () => {
      await queryClient.invalidateQueries({
        queryKey: groupKeys.joinRequests(groupId, 'PENDING'),
        refetchType: 'active',
      });
      run(<Toast>모임 신청이 거절되었습니다.</Toast>);
    },
  });
 
  const handleApprove = useCallback(
    (targetUserId: string) => {
      approveMutation.mutate(targetUserId);
    },
    [approveMutation],
  );
 
  const handleReject = useCallback(
    (targetUserId: string) => {
      rejectMutation.mutate(targetUserId);
    },
    [rejectMutation],
  );
 
  if (isLoading && !data && !error) {
    return <PendingMembersSkeleton />;
  }
 
  if (isForbidden) {
    return null;
  }
 
  if (error && (!('status' in error) || error.status !== 403)) {
    return (
      <section className={`relative ${PENDING_MEMBERS_MIN_HEIGHT}`}>
        <div className='flex-center h-full text-gray-600'>데이터를 불러오는데 실패했습니다.</div>
      </section>
    );
  }
 
  if (!data || data.items.length === 0) {
    return (
      <section className={`relative ${PENDING_MEMBERS_MIN_HEIGHT}`}>
        <EmptyState>승인 대기 중인 멤버가 없습니다</EmptyState>
      </section>
    );
  }
 
  return (
    <section className='mt-5 px-4 pb-5'>
      <ul className='space-y-4'>
        {data.items.map((member) => (
          <li key={`${member.userId}-${member.groupUserId}-${member.joinedAt}`}>
            <PendingMemberCard
              member={member}
              onApprove={() => handleApprove(String(member.userId))}
              onReject={() => handleReject(String(member.userId))}
            />
          </li>
        ))}
      </ul>
    </section>
  );
};