diff --git a/apps/web/app/_components/PlaceListItem/PlaceListItem.tsx b/apps/web/app/_components/PlaceListItem/PlaceListItem.tsx index e034b0a..2e49f6c 100644 --- a/apps/web/app/_components/PlaceListItem/PlaceListItem.tsx +++ b/apps/web/app/_components/PlaceListItem/PlaceListItem.tsx @@ -6,6 +6,7 @@ import { Text } from '@repo/ui/components/Text' import { Icon } from '@repo/ui/components/Icon' import { Chip } from '@repo/ui/components/Chip' import { Column, Flex } from '@repo/ui/components/Layout' +import { PlaceListItemSkeleton } from './PlaceListItemSkeleton' type Props = { showCategory?: boolean @@ -53,3 +54,5 @@ export const PlaceListItem = ({ ) } + +PlaceListItem.Skeleton = PlaceListItemSkeleton diff --git a/apps/web/app/_components/PlaceListItem/PlaceListItemSkeleton.tsx b/apps/web/app/_components/PlaceListItem/PlaceListItemSkeleton.tsx new file mode 100644 index 0000000..3eec057 --- /dev/null +++ b/apps/web/app/_components/PlaceListItem/PlaceListItemSkeleton.tsx @@ -0,0 +1,29 @@ +'use client' + +import { Skeleton } from '@heroui/react' +import { Column, Flex } from '@repo/ui/components/Layout' +import { cn } from '@repo/ui/utils/cn' + +type Props = { + count?: number +} + +export const PlaceListItemSkeleton = ({ count = 3 }: Props) => { + return ( +
+ {Array.from({ length: count }).map((_, index) => ( + + + + + + + + + ))} +
+ ) +} diff --git a/apps/web/app/_components/RankingPlaceList/MostLikesPlaces/MostLikesPlaces.tsx b/apps/web/app/_components/RankingPlaceList/MostLikesPlaces/MostLikesPlaces.tsx deleted file mode 100644 index 8df4501..0000000 --- a/apps/web/app/_components/RankingPlaceList/MostLikesPlaces/MostLikesPlaces.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client' - -import { useSuspenseQuery } from '@tanstack/react-query' -import { usePlaceQueries } from '@/_apis/queries/place' -import { RankingPlaceList } from '@/_components/RankingPlaceList' -import { useCampusStore } from '@/_store/campus' - -export const MostLikesPlaces = () => { - const { campus } = useCampusStore() - const { data } = useSuspenseQuery(usePlaceQueries.byRanking('likes', campus)) - - return ( - - ) -} diff --git a/apps/web/app/_components/RankingPlaceList/MostLikesPlaces/index.tsx b/apps/web/app/_components/RankingPlaceList/MostLikesPlaces/index.tsx deleted file mode 100644 index e57fea4..0000000 --- a/apps/web/app/_components/RankingPlaceList/MostLikesPlaces/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { MostLikesPlaces } from './MostLikesPlaces' diff --git a/apps/web/app/_components/RankingPlaceList/MostViewsPlaces/MostViewsPlaces.tsx b/apps/web/app/_components/RankingPlaceList/MostViewsPlaces/MostViewsPlaces.tsx deleted file mode 100644 index 999d8ba..0000000 --- a/apps/web/app/_components/RankingPlaceList/MostViewsPlaces/MostViewsPlaces.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { useSuspenseQuery } from '@tanstack/react-query' -import { usePlaceQueries } from '@/_apis/queries/place' -import { RankingPlaceList } from '@/_components/RankingPlaceList' -import { useCampusStore } from '@/_store/campus' - -export const MostViewsPlaces = () => { - const { campus } = useCampusStore() - const { data } = useSuspenseQuery(usePlaceQueries.byRanking('views', campus)) - - return -} diff --git a/apps/web/app/_components/RankingPlaceList/MostViewsPlaces/index.tsx b/apps/web/app/_components/RankingPlaceList/MostViewsPlaces/index.tsx deleted file mode 100644 index 4bb7554..0000000 --- a/apps/web/app/_components/RankingPlaceList/MostViewsPlaces/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { MostViewsPlaces } from './MostViewsPlaces' diff --git a/apps/web/app/_components/RankingPlaceList/RankingPlaceList.tsx b/apps/web/app/_components/RankingPlaceList/RankingPlaceList.tsx index 83b1ee6..6367fe3 100644 --- a/apps/web/app/_components/RankingPlaceList/RankingPlaceList.tsx +++ b/apps/web/app/_components/RankingPlaceList/RankingPlaceList.tsx @@ -1,5 +1,11 @@ +'use client' + +import { Suspense } from 'react' +import { useSuspenseQuery } from '@tanstack/react-query' +import { useCampusStore } from '@/_store/campus' +import { usePlaceQueries } from '@/_apis/queries/place' import type { IconType } from '@repo/ui/components/Icon' -import type { BasePlace } from '@/_apis/schemas/place' +import type { RankingPlaceSort } from '@/_apis/schemas/place' import { Column } from '@repo/ui/components/Layout' import { SubTitle } from '@/_components/SubTitle' import { PlaceListItem } from '@/_components/PlaceListItem' @@ -7,22 +13,39 @@ import { PlaceListItem } from '@/_components/PlaceListItem' type Props = { title: string icon: IconType - places: BasePlace[] + rankingPlaceSort: RankingPlaceSort } -export const RankingPlaceList = ({ title, icon, places }: Props) => { +export const RankingPlaceList = ({ title, icon, rankingPlaceSort }: Props) => { return ( -
    - {places.map((place, index) => ( - - ))} -
+ }> + +
) } + +const PlaceList = ({ + rankingPlaceSort, +}: { + rankingPlaceSort: RankingPlaceSort +}) => { + const { campus } = useCampusStore() + const { data: places } = useSuspenseQuery( + usePlaceQueries.byRanking(rankingPlaceSort, campus), + ) + + return ( + + ) +} diff --git a/apps/web/app/_components/RankingPlaceList/index.tsx b/apps/web/app/_components/RankingPlaceList/index.tsx index fa5f639..065d851 100644 --- a/apps/web/app/_components/RankingPlaceList/index.tsx +++ b/apps/web/app/_components/RankingPlaceList/index.tsx @@ -1,3 +1 @@ export { RankingPlaceList } from './RankingPlaceList' -export { MostViewsPlaces } from './MostViewsPlaces' -export { MostLikesPlaces } from './MostLikesPlaces' diff --git a/apps/web/app/categories/[id]/CategoryDetailPage.tsx b/apps/web/app/categories/[id]/CategoryDetailPage.tsx index 39015f3..38bd4a9 100644 --- a/apps/web/app/categories/[id]/CategoryDetailPage.tsx +++ b/apps/web/app/categories/[id]/CategoryDetailPage.tsx @@ -1,28 +1,25 @@ 'use client' - -import { usePathname } from 'next/navigation' +import { Suspense, useEffect } from 'react' import { useSuspenseQuery } from '@tanstack/react-query' import { useCategoryQueries } from '@/_apis/queries/category' +import { useCategoryIdFromUrl } from './_hooks/useCategoryIdFromUrl' + import { Icon } from '@repo/ui/components/Icon' import { Text } from '@repo/ui/components/Text' import { Header } from '@repo/ui/components/Header' import { Flex } from '@repo/ui/components/Layout' import { HeaderBackButton } from '@/_components/HeaderBackButton' +import { PlaceListItem } from '@/_components/PlaceListItem' import { RowCategories, Places } from './_components' -import { Suspense, useEffect } from 'react' -import { Spinner } from '@heroui/react' +import { SwipeableArea } from './_components/SwipeableArea' export const CategoryDetailPage = () => { const { data: categories } = useSuspenseQuery(useCategoryQueries.list()) - const activeCategoryId = usePathname().split('/')[2] || '0' + const [categoryId, setCategoryId] = useCategoryIdFromUrl() const activeCategory = categories.find( - (category) => category.id === activeCategoryId, + (category) => category.id === categoryId, ) - const setIdFunc = (id: string) => { - window.history.replaceState(null, '', `/categories/${id}`) - } - useEffect(() => { document.title = `공주대 맛집 | ${activeCategory?.name}` }, [activeCategory]) @@ -42,14 +39,15 @@ export const CategoryDetailPage = () => { className={'border-b-1 border-gray-50'} /> - {/*Todo: 맛집 리스트 스켈레톤으로 변경하기*/} - }> - - + + }> + + + ) } diff --git a/apps/web/app/categories/[id]/_components/Places/Places.tsx b/apps/web/app/categories/[id]/_components/Places/Places.tsx index 3932cca..12fac79 100644 --- a/apps/web/app/categories/[id]/_components/Places/Places.tsx +++ b/apps/web/app/categories/[id]/_components/Places/Places.tsx @@ -1,5 +1,4 @@ import { useSuspenseQuery } from '@tanstack/react-query' -import { motion, PanInfo } from 'motion/react' import { useCampusStore } from '@/_store/campus' import { usePlaceQueries } from '@/_apis/queries/place' import { PlaceListItem } from '@/_components/PlaceListItem' @@ -7,68 +6,31 @@ import { VerticalScrollArea } from '@repo/ui/components/Layout' import { EmptyPlaces } from './EmptyPlaces' type Props = { - id: string - setIdFunc: (id: string) => void + categoryId: string } -// 스와이프 감도 -const SWIPE_CONFIDENCE_THRESHOLD = 20 - -export const Places = ({ id, setIdFunc }: Props) => { +export const Places = ({ categoryId }: Props) => { const { campus } = useCampusStore() const { data: places } = useSuspenseQuery( - usePlaceQueries.byCategory(id, campus), + usePlaceQueries.byCategory(categoryId, campus), ) - const currentCategoryId = Number(id) - const onDragEnd = ( - _e: MouseEvent | TouchEvent | PointerEvent, - { offset, velocity }: PanInfo, - ) => { - const swipePower = Math.abs(offset.x) * velocity.x - - if (swipePower < -SWIPE_CONFIDENCE_THRESHOLD) { - if (currentCategoryId < 15) { - setIdFunc(String(currentCategoryId + 1)) - } - } else if (swipePower > SWIPE_CONFIDENCE_THRESHOLD) { - if (currentCategoryId > 1) { - setIdFunc(String(currentCategoryId - 1)) - } - } - } - - const content = - places.length === 0 ? ( - - ) : ( - - {places.map((place, index) => ( - - ))} - - ) - return ( -
- = 15 ? 0 : undefined, - }} - dragElastic={0.2} - onDragEnd={onDragEnd} - className='relative h-full w-full bg-white' - > - {content} - -
+ <> + {places.length === 0 ? ( + + ) : ( + + {places.map((place, index) => ( + + ))} + + )} + ) } diff --git a/apps/web/app/categories/[id]/_components/RowCategories/RowCategories.tsx b/apps/web/app/categories/[id]/_components/RowCategories/RowCategories.tsx index b189b50..a0b320b 100644 --- a/apps/web/app/categories/[id]/_components/RowCategories/RowCategories.tsx +++ b/apps/web/app/categories/[id]/_components/RowCategories/RowCategories.tsx @@ -5,11 +5,15 @@ import { cn } from '@repo/ui/utils/cn' type Props = { categories: Category[] - id: string - setIdFunc: (id: string) => void + categoryId: string + setCategoryId: (id: string) => void } -export const RowCategories = ({ id, categories, setIdFunc }: Props) => { +export const RowCategories = ({ + categoryId, + categories, + setCategoryId, +}: Props) => { return (
@@ -17,9 +21,9 @@ export const RowCategories = ({ id, categories, setIdFunc }: Props) => { { - setIdFunc(category.id) + setCategoryId(category.id) }} /> ))} diff --git a/apps/web/app/categories/[id]/_components/SwipeableArea/SwipeableArea.tsx b/apps/web/app/categories/[id]/_components/SwipeableArea/SwipeableArea.tsx new file mode 100644 index 0000000..72432b5 --- /dev/null +++ b/apps/web/app/categories/[id]/_components/SwipeableArea/SwipeableArea.tsx @@ -0,0 +1,52 @@ +import { motion, PanInfo } from 'motion/react' + +type Props = { + categoryId: string + setCategoryId: (id: string) => void + children: React.ReactNode +} + +const SWIPE_CONFIDENCE_THRESHOLD = 20 + +export const SwipeableArea = ({ + categoryId, + setCategoryId, + children, +}: Props) => { + const NumberToCategoryId = Number(categoryId) + + const onDragEnd = ( + _e: MouseEvent | TouchEvent | PointerEvent, + { offset, velocity }: PanInfo, + ) => { + const swipePower = Math.abs(offset.x) * velocity.x + + if (swipePower < -SWIPE_CONFIDENCE_THRESHOLD) { + if (NumberToCategoryId < 15) { + setCategoryId(String(NumberToCategoryId + 1)) + } + } else if (swipePower > SWIPE_CONFIDENCE_THRESHOLD) { + if (NumberToCategoryId > 1) { + setCategoryId(String(NumberToCategoryId - 1)) + } + } + } + + return ( +
+ = 15 ? 0 : undefined, + }} + dragElastic={0.2} + onDragEnd={onDragEnd} + className='relative h-full w-full bg-white' + > + {children} + +
+ ) +} diff --git a/apps/web/app/categories/[id]/_components/SwipeableArea/index.ts b/apps/web/app/categories/[id]/_components/SwipeableArea/index.ts new file mode 100644 index 0000000..9e9a49d --- /dev/null +++ b/apps/web/app/categories/[id]/_components/SwipeableArea/index.ts @@ -0,0 +1 @@ +export { SwipeableArea } from './SwipeableArea' diff --git a/apps/web/app/categories/[id]/_hooks/useCategoryIdFromUrl.ts b/apps/web/app/categories/[id]/_hooks/useCategoryIdFromUrl.ts new file mode 100644 index 0000000..2efec16 --- /dev/null +++ b/apps/web/app/categories/[id]/_hooks/useCategoryIdFromUrl.ts @@ -0,0 +1,11 @@ +import { usePathname } from 'next/navigation' +import { CLIENT_PATH } from '@/_constants/path' + +export const useCategoryIdFromUrl = () => { + const categoryId = usePathname().split('/')[2] || '0' + const setCategoryId = (id: string) => { + window.history.replaceState(null, '', CLIENT_PATH.CATEGORY_DETAIL(id)) + } + + return [categoryId, setCategoryId] as const +} diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 61aeadf..3ba3273 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -6,10 +6,7 @@ import { Flex, VerticalScrollArea } from '@repo/ui/components/Layout' import { Icon } from '@repo/ui/components/Icon' import { Text } from '@repo/ui/components/Text' import { Divider } from '@repo/ui/components/Divider' -import { - MostLikesPlaces, - MostViewsPlaces, -} from '@/_components/RankingPlaceList' +import { RankingPlaceList } from '@/_components/RankingPlaceList' import { HydrationBoundaryPage } from '@/HydrationBoundaryPage' import { Banner } from '@repo/ui/components/Banner' import { Categories } from '@/_components/Categories' @@ -24,11 +21,7 @@ export const dynamic = 'force-dynamic' export default function Page() { return ( - { - await queryClient.prefetchQuery(useCategoryQueries.list()) - }} - > + <>
@@ -42,18 +35,32 @@ export default function Page() { /> - + { + await queryClient.prefetchQuery(useCategoryQueries.list()) + }} + > + + , , ]} /> - + - + - + ) }