diff --git a/src/apis/apis.ts b/src/apis/apis.ts index dca3df88..33e39c8e 100644 --- a/src/apis/apis.ts +++ b/src/apis/apis.ts @@ -15,11 +15,16 @@ export const getPoseDetail = (poseId: number) => export const getPoseTalk = () => publicApi.get('/pose/talk'); -export const getPoseFeed = (peopleCount: number, frameCount: number, tags: string) => - publicApi.get(`/pose`, { +export const getPoseFeed = async ( + peopleCount: number, + frameCount: number, + tags: string, + pageNumber: number +) => + await publicApi.get(`/pose`, { params: { frameCount, - pageNumber: 0, + pageNumber, peopleCount, tags, }, diff --git a/src/apis/queries.ts b/src/apis/queries.ts index 61768095..b7291757 100644 --- a/src/apis/queries.ts +++ b/src/apis/queries.ts @@ -1,4 +1,9 @@ -import { UseQueryOptions, useQuery } from '@tanstack/react-query'; +import { + UseInfiniteQueryOptions, + UseQueryOptions, + useInfiniteQuery, + useQuery, +} from '@tanstack/react-query'; import { FilterTagsResponse, @@ -30,12 +35,22 @@ export const usePoseTalkQuery = (options?: UseQueryOptions) => export const usePoseFeedQuery = ( { peopleCount, frameCount, tags }: FilterState, - options?: UseQueryOptions + options?: UseInfiniteQueryOptions ) => - useQuery( + useInfiniteQuery( ['poseFeed', peopleCount, frameCount, tags], - () => getPoseFeed(peopleCount, frameCount, tags.join(',')), + ({ pageParam = '' }) => getPoseFeed(peopleCount, frameCount, tags.join(','), pageParam), { + getNextPageParam: (lastPage) => { + let target = lastPage.filteredContents; + if (lastPage.recommendation) { + target = lastPage.recommendedContents; + } else { + target = lastPage.filteredContents; + } + if (target.last) return false; + return target.number + 1; + }, ...options, } ); diff --git a/src/app/(Main)/feed/components/PhotoList.tsx b/src/app/(Main)/feed/components/PhotoList.tsx index efb16283..75f44c37 100644 --- a/src/app/(Main)/feed/components/PhotoList.tsx +++ b/src/app/(Main)/feed/components/PhotoList.tsx @@ -7,7 +7,7 @@ interface PhotoList { export default function PhotoList({ data }: PhotoList) { return ( -
+ <> {data ? ( data.map((item) => ( )} -
+ ); } diff --git a/src/app/(Main)/feed/page.tsx b/src/app/(Main)/feed/page.tsx index 97cebd89..7c955a58 100644 --- a/src/app/(Main)/feed/page.tsx +++ b/src/app/(Main)/feed/page.tsx @@ -1,7 +1,7 @@ 'use client'; -import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; +import { useCallback } from 'react'; import EmptyCase from './components/EmptyCase'; import FilterSheet from './components/FilterSheet'; @@ -9,16 +9,17 @@ import FilterTab from './components/FilterTab'; import PhotoList from './components/PhotoList'; import { usePoseFeedQuery } from '@/apis'; import { Spacing } from '@/components/Spacing'; +import { URL } from '@/constants/url'; import useDidMount from '@/hooks/useDidMount'; import useFilterState from '@/hooks/useFilterState'; -import { URL } from '@/constants/url'; +import useIntersect from '@/hooks/useObserver'; export default function Feed() { const params = useSearchParams(); const router = useRouter(); const { filterState, updateFilterState } = useFilterState(); - const { data, isFetched } = usePoseFeedQuery(filterState); + const { data, fetchNextPage, hasNextPage, isLoading } = usePoseFeedQuery(filterState); useDidMount(() => { if (!params.get('filter')) return; @@ -30,12 +31,20 @@ export default function Feed() { router.replace('/feed'); }); + const onIntersect = useCallback(async () => { + if (hasNextPage && !isLoading) { + await fetchNextPage(); + } + }, [fetchNextPage, hasNextPage, isLoading]); + + const target = useIntersect(onIntersect); + return ( <>
- {data?.recommendation && ( + {data?.pages[0].recommendation ? ( <> -

이런 포즈는 어때요?

- +
+ {data?.pages.map((page) => ( + + ))} +
+ ) : ( +
+ {data?.pages.map((page) => ( + + ))} +
)} - {isFetched ? : } +
diff --git a/src/app/(Main)/pick/components/PickSection.tsx b/src/app/(Main)/pick/components/PickSection.tsx index 1fa47397..981ca477 100644 --- a/src/app/(Main)/pick/components/PickSection.tsx +++ b/src/app/(Main)/pick/components/PickSection.tsx @@ -48,7 +48,7 @@ export default function PickSection() { src={image || '/images/image-frame.png'} fill priority - className={clsx({ hidden: isLoading }, 'cursor-pointer')} + className={clsx({ hidden: isLoading }, 'cursor-pointer object-contain')} onClick={() => open(({ exit }) => ( @@ -65,7 +65,6 @@ export default function PickSection() { )) } - style={{ objectFit: 'contain' }} alt="이미지" />
diff --git a/src/app/globals.css b/src/app/globals.css index 2ec19efa..1dff8366 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -14,16 +14,18 @@ display: none; } -html { - font-size: calc(16vw / 440 * 100); -} - @media (min-width: 440px) { html { font-size: 16px; } } +@supports (-webkit-touch-callout: none) { + .h-screen { + height: -webkit-fill-available; + } +} + h1 { font-weight: 700; font-size: 3rem; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 76fe3a44..3842255a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -19,7 +19,7 @@ const DEFAULT_OG_IMAGE = '/images/main_star.png'; export const metadata: Metadata = { metadataBase: new URL(BASE_SITE_URL), title: { - template: `${DEFAULT_OG_TITLE} / %s `, // 요건 뭔가용? + template: DEFAULT_OG_TITLE, default: DEFAULT_OG_TITLE, }, description: DEFAULT_OG_DESC, @@ -39,6 +39,9 @@ export const metadata: Metadata = { maximumScale: 1, userScalable: false, }, + icons: { + icon: './favicon.ico', + }, manifest: '/manifest.json', themeColor: '#ffffff', }; diff --git a/src/hooks/useObserver.tsx b/src/hooks/useObserver.tsx new file mode 100644 index 00000000..e0cd9144 --- /dev/null +++ b/src/hooks/useObserver.tsx @@ -0,0 +1,16 @@ +import { useEffect, useRef } from 'react'; + +const useIntersect = (callback: () => void, options?: IntersectionObserverInit) => { + const target = useRef(null); + + useEffect(() => { + if (!target.current) return; + const observer = new IntersectionObserver(callback, options); + observer.observe(target.current); + return () => observer.disconnect(); + }, [callback, options]); + + return target; +}; + +export default useIntersect;