diff --git a/hooks/useIntersectionObserver.tsx b/hooks/useIntersectionObserver.tsx index 8be1396..aa7b40b 100644 --- a/hooks/useIntersectionObserver.tsx +++ b/hooks/useIntersectionObserver.tsx @@ -1,31 +1,27 @@ import { RefObject, useEffect } from 'react'; import { AxiosError } from 'axios'; import { FetchNextPageOptions, InfiniteQueryObserverResult } from 'react-query'; -import { MainContentsResponse } from '~/data/services/services.model'; import { SuccessResponse } from '~/shared/types'; -export interface IUseIntersectionObserverProps +export interface IUseIntersectionObserverProps extends IntersectionObserverInit { target: RefObject; enabled?: boolean; onIntersect: ( options?: FetchNextPageOptions | undefined, ) => Promise< - InfiniteQueryObserverResult< - SuccessResponse, - AxiosError - > + InfiniteQueryObserverResult, AxiosError> >; } -export function useIntersectionObserver({ +export function useIntersectionObserver({ root, rootMargin = '0px', threshold = 1.0, target, enabled = true, onIntersect, -}: IUseIntersectionObserverProps) { +}: IUseIntersectionObserverProps) { useEffect(() => { if (!enabled) { return; diff --git a/pages/index.tsx b/pages/index.tsx index bb23ce4..94ddd84 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -141,8 +141,7 @@ const Main: NextPage = function () { })} ))} - {/* 추가 데이터를 fetch 할 때 보여주는 스켈레톤 UI */} - {isFetchingNextPage ? : undefined} + {isFetchingNextPage && } ); } diff --git a/pages/writers.tsx b/pages/writers.tsx new file mode 100644 index 0000000..b062e31 --- /dev/null +++ b/pages/writers.tsx @@ -0,0 +1,189 @@ +import { useState, useRef } from 'react'; +import { NextPage } from 'next'; +import { dehydrate, QueryClient } from 'react-query'; +import Router from 'next/router'; +import { useIntersectionObserver, useSearch } from '~/hooks'; +import { + ContentsSearchParams, + MainWritersResponse, + Writer, +} from '~/data/services/services.model'; +import { getMainWriters } from '~/data/services/services.api'; +import { Box, Search, styled, Button } from '@nolmungshemung/ui-kits'; +import { useInfinityWriters } from '~/data/services/services.hooks'; +import { SuccessResponse } from '~/shared/types'; +import { StyledCard } from '~/components/Main/Card'; +import { SkeletonCard } from '~/components/Skeleton'; +import { DEFAULT_SEARCH_RANGE } from '~/shared/constants/pagination'; + +const StyledMain = styled('div', { + gridArea: 'main', + display: 'grid', + gridTemplateAreas: ` + "top" + "result" + `, + justifyItems: 'center', +}); + +const SytledTopArea = styled(Box, { + gridArea: 'top', +}); + +const StyledCardList = styled(Box, { + gridArea: 'result', + display: 'grid', + gridTemplateColumns: 'repeat(4, 1fr)', + gridTemplateRows: 'repeat(5, 1fr)', + columnGap: '$sp-24', + rowGap: '$sp-16', + marginTop: '$sp-32', +}); + +const initialState: ContentsSearchParams = { + start: 0, + count: DEFAULT_SEARCH_RANGE, + baseTime: Date.now(), + keyword: '', +}; + +const Main: NextPage = function () { + const loadMoreRef = useRef(null); + + const [searchParams, setSearchParams] = + useState(initialState); + + const { onChange, onEnter, onSearch } = useSearch((keyword: string) => { + setSearchParams((prev) => ({ + ...prev, + baseTime: Date.now(), + keyword, + })); + }); + + const { isLoading, data, isFetchingNextPage, fetchNextPage, hasNextPage } = + useInfinityWriters(searchParams, { + getNextPageParam: (lastPage) => { + const { + data: { isLast, start }, + } = lastPage; + + return !isLast ? start + DEFAULT_SEARCH_RANGE : false; + }, + }); + + const pages = (data?.pages ?? []) as SuccessResponse[]; + const flatMainWriters = pages + .map((page) => page.data.mainWriterList.map((writer: Writer) => writer)) + .flat(); + + useIntersectionObserver({ + target: loadMoreRef, + enabled: hasNextPage, + onIntersect: fetchNextPage, + }); + + const onContentsSearchButtonClick = () => { + Router.push('/'); + }; + + const onCardClick = (writerId: string) => { + Router.push({ + pathname: '/feeds', + query: { writerId }, + }); + }; + + const renderCardList = () => { + // 데이터 최초 로딩 시 보여주는 스켈레톤 UI + if (isLoading) { + return ; + } + + if (pages.length > 0) { + return ( + <> + {flatMainWriters.map((writer: Writer, index: number) => ( + onCardClick(writer.writerId)} + css={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }} + > + {writer.writerName} + + ))} + {isFetchingNextPage && } + + ); + } + + return undefined; + }; + + return ( + + + + + + + + + {renderCardList()} + + + + ); +}; + +export async function getServerSideProps() { + try { + const queryClient = new QueryClient(); + await queryClient.prefetchInfiniteQuery( + ['/services/main_writers', initialState], + getMainWriters, + ); + return { + props: { + dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), + }, + }; + } catch (e) { + console.error(e); + } +} + +export default Main;