diff --git a/src/app/page.tsx b/src/app/page.tsx index c185752d..abafb5ff 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,15 +1,12 @@ import type { Metadata } from 'next'; -import { InfiniteData } from '@tanstack/react-query'; +import { Suspense } from 'react'; -import { API } from '@/api'; import GroupList from '@/components/pages/group-list'; import { GroupSearchBar } from '@/components/pages/group-list/group-search-bar'; +import { CardSkeleton } from '@/components/shared/card/card-skeleton'; import { GROUP_LIST_PAGE_SIZE } from '@/lib/constants/group-list'; import { generateHomeMetadata } from '@/lib/metadata/home'; -import { GetGroupsResponse } from '@/types/service/group'; - -export const dynamic = 'force-dynamic'; interface HomePageProps { searchParams: Promise<{ keyword?: string }>; @@ -20,26 +17,25 @@ export const generateMetadata = async ({ searchParams }: HomePageProps): Promise return await generateHomeMetadata(params.keyword); }; -export default async function HomePage({ searchParams }: HomePageProps) { - const params = await searchParams; - const keyword = params.keyword; - - const response = await API.groupService.getGroups({ - size: GROUP_LIST_PAGE_SIZE, - keyword, - }); - - // React Query의 useInfiniteQuery에 맞는 initialData 형태로 변환 - const initialData: InfiniteData = { - pages: [response], - pageParams: [undefined], // 첫 페이지는 cursor가 없으므로 undefined - }; - - // 초기 데이터를 전달해서 무한 스크롤 시작 +export default async function HomePage(_props: HomePageProps) { return ( <> - + }> + + ); } + +const GroupListSkeleton = () => ( +
+
+
+ {Array.from({ length: GROUP_LIST_PAGE_SIZE }).map((_, i) => ( + + ))} +
+
+
+); diff --git a/src/components/pages/group-list/group-list-content/index.tsx b/src/components/pages/group-list/group-list-content/index.tsx new file mode 100644 index 00000000..5df5ba23 --- /dev/null +++ b/src/components/pages/group-list/group-list-content/index.tsx @@ -0,0 +1,41 @@ +import { useRouter } from 'next/navigation'; + +import Card from '@/components/shared/card'; +import { formatDateTime } from '@/lib/formatDateTime'; +import { GroupListItemResponse } from '@/types/service/group'; + +interface GroupListContentProps { + items: GroupListItemResponse[]; + keyword?: string; +} + +export const GroupListContent = ({ items, keyword }: GroupListContentProps) => { + const router = useRouter(); + const hasKeyword = Boolean(keyword); + + return ( +
+ {items.map((meeting) => ( + router.push(`/group/${meeting.id}`)} + /> + ))} +
+ ); +}; diff --git a/src/components/pages/group-list/group-list-empty/index.tsx b/src/components/pages/group-list/group-list-empty/index.tsx new file mode 100644 index 00000000..1bbff78c --- /dev/null +++ b/src/components/pages/group-list/group-list-empty/index.tsx @@ -0,0 +1,32 @@ +import { useRouter } from 'next/navigation'; + +import { EmptyState } from '@/components/layout/empty-state'; +import { Button } from '@/components/ui'; +import { + GROUP_LIST_CREATE_BUTTON_WIDTH, + GROUP_LIST_EMPTY_BUTTON_TOP_MARGIN, + GROUP_LIST_EMPTY_MIN_HEIGHT, +} from '@/lib/constants/group-list'; + +export const GroupListEmpty = () => { + const router = useRouter(); + + return ( +
+ + 아직 모임이 없어요. +
+ 지금 바로 모임을 만들어보세요! +
+ + +
+ ); +}; diff --git a/src/components/pages/group-list/group-list-infinite-scroll/index.tsx b/src/components/pages/group-list/group-list-infinite-scroll/index.tsx new file mode 100644 index 00000000..6007ad49 --- /dev/null +++ b/src/components/pages/group-list/group-list-infinite-scroll/index.tsx @@ -0,0 +1,48 @@ +import { type RefObject } from 'react'; + +interface GroupListInfiniteScrollProps { + sentinelRef: RefObject; + hasNextPage: boolean; + isFetchingNextPage: boolean; + completedMessage: string; + hasError: boolean; +} + +export const GroupListInfiniteScroll = ({ + sentinelRef, + hasNextPage, + isFetchingNextPage, + completedMessage, + hasError, +}: GroupListInfiniteScrollProps) => { + if (hasNextPage && !hasError) { + return ( + <> +