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
27 changes: 0 additions & 27 deletions src/app/group/[groupId]/pending/page.tsx

This file was deleted.

10 changes: 8 additions & 2 deletions src/components/layout/empty-state/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ interface Props {

export const EmptyState = ({ children }: Props) => {
return (
<section className='flex-col-center text-text-sm-medium pointer-events-none absolute inset-0 gap-2 text-gray-600'>
<Image width={140} alt='빈 화면' height={140} src='/images/image-empty.png' />
<section className='flex-col-center text-text-sm-medium absolute inset-0 text-center text-gray-600'>
<Image
width={140}
className='mb-2'
alt='빈 화면'
height={140}
src='/images/image-empty.png'
/>
{children}
</section>
);
Expand Down
102 changes: 72 additions & 30 deletions src/components/pages/group-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { useRouter, useSearchParams } from 'next/navigation';

import { InfiniteData } from '@tanstack/react-query';

import { EmptyState } from '@/components/layout/empty-state';
import { ErrorMessage } from '@/components/shared';
import Card from '@/components/shared/card';
import { Button } from '@/components/ui';
import { useInfiniteGroupList } from '@/hooks/use-group/use-group-infinite-list';
import { useIntersectionObserver } from '@/hooks/use-intersection-observer';
import { INTERSECTION_OBSERVER_THRESHOLD } from '@/lib/constants/group-list';
Expand All @@ -17,6 +19,14 @@ interface GroupListProps {
initialKeyword?: string;
}

const SearchResultCount = ({ count }: { count: number }) => (
<div className='mt-4 flex h-5 items-center pl-2'>
<span className='text-text-sm-medium text-gray-800'>검색결과</span>
<span className='text-text-sm-medium text-mint-600 ml-1'>{count}</span>
<span className='text-text-sm-medium text-gray-800'>개</span>
</div>
);

export default function GroupList({ initialData, initialKeyword }: GroupListProps) {
const router = useRouter();
const searchParams = useSearchParams();
Expand All @@ -41,47 +51,79 @@ export default function GroupList({ initialData, initialKeyword }: GroupListProp
threshold: INTERSECTION_OBSERVER_THRESHOLD,
});

const hasKeyword = Boolean(keyword);
const hasNoItems = items.length === 0 && !error;

return (
<section className='min-h-screen bg-[#F1F5F9]'>
<div className='flex w-full flex-col gap-4 px-4 py-4'>
<div className='flex w-full flex-col px-4'>
{error && items.length === 0 && (
<ErrorMessage
className='py-12'
message={error.message}
onRetry={() => window.location.reload()}
/>
<div className='py-4'>
<ErrorMessage
className='py-12'
message={error.message}
onRetry={() => window.location.reload()}
/>
</div>
)}

{items.length === 0 && !error ? (
<div className='py-8 text-center text-gray-500'>모임이 없습니다.</div>
) : (
items.map((meeting) => (
<Card
key={meeting.id}
dateTime={formatDateTime(meeting.startTime)}
images={meeting.images}
location={meeting.location}
maxParticipants={meeting.maxParticipants}
nickName={meeting.createdBy.nickName}
participantCount={meeting.participantCount}
profileImage={meeting.createdBy.profileImage}
tags={meeting.tags}
title={meeting.title}
onClick={() => router.push(`/group/${meeting.id}`)}
/>
))
{hasKeyword && <SearchResultCount count={items.length} />}

{!hasKeyword && hasNoItems && (
<div className='relative flex min-h-[calc(100vh-200px)] flex-col items-center justify-center py-8'>
<EmptyState>
아직 모임이 없어요.
<br />
지금 바로 모임을 만들어보세요!
</EmptyState>

<Button
className='bg-mint-500 text-text-sm-bold text-mono-white hover:bg-mint-600 active:bg-mint-700 relative z-10 mt-[250px] h-10 w-[112px] rounded-xl'
onClick={() => router.push('/create-group')}
>
모임 만들기
</Button>
</div>
)}

{hasKeyword && hasNoItems && (
<div className='relative mt-[174px] flex h-[200px] flex-col items-center justify-center'>
<EmptyState>검색 결과가 없어요.</EmptyState>
</div>
)}

{items.length > 0 && (
<div className={`flex w-full flex-col gap-4 ${hasKeyword ? 'mt-3' : 'py-4'}`}>
{items.map((meeting) => (
<Card
key={meeting.id}
dateTime={formatDateTime(meeting.startTime)}
images={meeting.images}
location={meeting.location}
maxParticipants={meeting.maxParticipants}
nickName={meeting.createdBy.nickName}
participantCount={meeting.participantCount}
profileImage={meeting.createdBy.profileImage}
tags={meeting.tags}
title={meeting.title}
onClick={() => router.push(`/group/${meeting.id}`)}
/>
))}
</div>
)}

{error && items.length > 0 && (
<ErrorMessage
className='py-8'
message={error.message}
onRetry={() => window.location.reload()}
/>
<div className='py-4'>
<ErrorMessage
className='py-8'
message={error.message}
onRetry={() => window.location.reload()}
/>
</div>
)}

{/* sentinel 요소 생성: hasNextPage가 true이고 에러가 없으면 렌더 */}
{hasNextPage && !error && <div ref={sentinelRef} className='h-1' />}
{hasNextPage && !error && items.length > 0 && <div ref={sentinelRef} className='h-1' />}

{/* hasNextPage가 false이면 모든 데이터를 불러온 상태 */}
{!hasNextPage && items.length > 0 && !error && (
Expand Down