Skip to content

Commit

Permalink
feat: 작가검색 화면 개발
Browse files Browse the repository at this point in the history
메인 작품검색 화면을 참고하여 작가검색 화면 개발

close #39
  • Loading branch information
ppark2ya committed Feb 12, 2022
1 parent a5d41a7 commit 82c14d2
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 10 deletions.
12 changes: 4 additions & 8 deletions hooks/useIntersectionObserver.tsx
Original file line number Diff line number Diff line change
@@ -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<T = unknown>
extends IntersectionObserverInit {
target: RefObject<HTMLDivElement>;
enabled?: boolean;
onIntersect: (
options?: FetchNextPageOptions | undefined,
) => Promise<
InfiniteQueryObserverResult<
SuccessResponse<MainContentsResponse>,
AxiosError<any, any>
>
InfiniteQueryObserverResult<SuccessResponse<T>, AxiosError<any, any>>
>;
}

export function useIntersectionObserver({
export function useIntersectionObserver<T>({
root,
rootMargin = '0px',
threshold = 1.0,
target,
enabled = true,
onIntersect,
}: IUseIntersectionObserverProps) {
}: IUseIntersectionObserverProps<T>) {
useEffect(() => {
if (!enabled) {
return;
Expand Down
3 changes: 1 addition & 2 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ const Main: NextPage = function () {
})}
</Fragment>
))}
{/* 추가 데이터를 fetch 할 때 보여주는 스켈레톤 UI */}
{isFetchingNextPage ? <SkeletonCard /> : undefined}
{isFetchingNextPage && <SkeletonCard />}
</>
);
}
Expand Down
189 changes: 189 additions & 0 deletions pages/writers.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>(null);

const [searchParams, setSearchParams] =
useState<ContentsSearchParams>(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<MainWritersResponse>[];
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 <SkeletonCard count={20} />;
}

if (pages.length > 0) {
return (
<>
{flatMainWriters.map((writer: Writer, index: number) => (
<StyledCard
key={index}
onClick={() => onCardClick(writer.writerId)}
css={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Box>{writer.writerName}</Box>
</StyledCard>
))}
{isFetchingNextPage && <SkeletonCard />}
</>
);
}

return undefined;
};

return (
<StyledMain>
<SytledTopArea>
<Box
css={{
position: 'relative',
display: 'flex',
marginTop: '$sp-50',
justifyContent: 'center',
width: '1216px',
}}
>
<Search
placeholder="검색어를 입력해주세요."
onChange={onChange}
onEnter={onEnter}
onSearch={onSearch}
/>
<Button
color="white"
outline="black"
css={{
position: 'absolute',
right: '0',
cursor: 'pointer',
}}
onClick={onContentsSearchButtonClick}
>
작품검색
</Button>
</Box>
</SytledTopArea>
<StyledCardList>
{renderCardList()}
<Box
ref={loadMoreRef}
css={{
height: '$height-md',
}}
/>
</StyledCardList>
</StyledMain>
);
};

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;

0 comments on commit 82c14d2

Please sign in to comment.