diff --git a/package.json b/package.json index bec03869..3b0bff22 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "react-scroll": "^1.9.0", "recoil": "^0.7.7", "styled-components": "^6.1.3", + "swiper": "^11.0.5", "uuid": "^9.0.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11a4a49d..f2648091 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ dependencies: styled-components: specifier: ^6.1.3 version: 6.1.4(react-dom@18.2.0)(react@18.2.0) + swiper: + specifier: ^11.0.5 + version: 11.0.5 uuid: specifier: ^9.0.1 version: 9.0.1 @@ -4889,6 +4892,11 @@ packages: picocolors: 1.0.0 dev: false + /swiper@11.0.5: + resolution: {integrity: sha512-rhCwupqSyRnWrtNzWzemnBLMoyYuoDgGgspAm/8iBD3jCvAWycPLH4Z3TB0O5520DHLzMx94yUMH/B9Efpa48w==} + engines: {node: '>= 4.7.0'} + dev: false + /tailwindcss@3.4.0: resolution: {integrity: sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==} engines: {node: '>=14.0.0'} diff --git a/src/App.tsx b/src/App.tsx index d69b84b5..8c38d96c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { BrowserRouter } from 'react-router-dom'; import MainRouter from '@router/mainRouter'; import AuthRouter from '@router/authRouter'; +import ScrollToTop from '@router/ScrollToTop'; const queryClient = new QueryClient(); @@ -15,6 +16,7 @@ const App = () => { + diff --git a/src/components/Tours/CreateTripButton.tsx b/src/components/Tours/CreateTripButton.tsx index d00bc9d9..f131de28 100644 --- a/src/components/Tours/CreateTripButton.tsx +++ b/src/components/Tours/CreateTripButton.tsx @@ -1,9 +1,7 @@ -import { PlusIcon } from '@components/common/icons/Icons'; - const CreateTripButton = () => { return ( -
- +
+
); }; diff --git a/src/components/Tours/ToursCategory.tsx b/src/components/Tours/ToursCategory.tsx index 3f4eae00..d4e3a3fd 100644 --- a/src/components/Tours/ToursCategory.tsx +++ b/src/components/Tours/ToursCategory.tsx @@ -1,18 +1,35 @@ import { RegionTypes, ToursCategoryProps } from '@/@types/tours.types'; import ToursCategoryItem from './ToursCategoryItem'; import { getPopularRegion } from '@api/region'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import 'swiper/css'; +import ToursCategoryItemSkeleton from './ToursCategoryItemSkeleton'; +import { v4 as uuidv4 } from 'uuid'; import { useQuery } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +// import { useEffect, useState } from 'react'; const ToursCategory = ({ selectedRegion, setSelectedRegion, }: ToursCategoryProps) => { - const regionsQuery = useQuery({ + const { data, isLoading, error } = useQuery({ queryKey: ['regions'], queryFn: () => getPopularRegion(), }); - if (regionsQuery.error) { + const [showSkeleton, setShowSkeleton] = useState(isLoading); + + useEffect(() => { + if (isLoading) { + setShowSkeleton(true); + } else { + const timer = setTimeout(() => setShowSkeleton(false), 200); + return () => clearTimeout(timer); + } + }, [isLoading]); + + if (error) { console.log('error - 예외 처리'); } @@ -26,24 +43,41 @@ const ToursCategory = ({ }; // '전체' 항목 추가 - const regionsData = regionsQuery.data?.data.data.regions ?? []; + const regionsData = data?.data.data.regions ?? []; const regions = [ - { name: '전체', areaCode: 0, subAreaCode: 0 }, + { name: '전체', areaCode: uuidv4(), subAreaCode: 0 }, ...regionsData, ]; + if (showSkeleton) { + return ( +
+ + {Array.from({ length: 10 }, (_, index) => ( + + + + ))} + +
+ ); + } + return (
- {regions.map((region: RegionTypes, index: number) => { - return ( - - ); - })} + + {regions.map((region: RegionTypes) => { + return ( + + + + ); + })} +
); }; diff --git a/src/components/Tours/ToursCategoryItem.tsx b/src/components/Tours/ToursCategoryItem.tsx index 8b5e30f2..3e48384e 100644 --- a/src/components/Tours/ToursCategoryItem.tsx +++ b/src/components/Tours/ToursCategoryItem.tsx @@ -13,7 +13,7 @@ const ToursCategoryItem = ({ ); diff --git a/src/components/Tours/ToursCategoryItemSkeleton.tsx b/src/components/Tours/ToursCategoryItemSkeleton.tsx new file mode 100644 index 00000000..e60f5678 --- /dev/null +++ b/src/components/Tours/ToursCategoryItemSkeleton.tsx @@ -0,0 +1,9 @@ +const ToursCategoryItemSkeleton = () => { + return ( +
+
+
+ ); +}; + +export default ToursCategoryItemSkeleton; diff --git a/src/components/Tours/ToursItemSkeleton.tsx b/src/components/Tours/ToursItemSkeleton.tsx new file mode 100644 index 00000000..ca91d5d4 --- /dev/null +++ b/src/components/Tours/ToursItemSkeleton.tsx @@ -0,0 +1,16 @@ +const ToursItemSkeleton = () => { + return ( +
+
+
+
+

+
+
+
+
+
+ ); +}; + +export default ToursItemSkeleton; diff --git a/src/components/Tours/ToursList.tsx b/src/components/Tours/ToursList.tsx index 10a2f6cc..c3634df7 100644 --- a/src/components/Tours/ToursList.tsx +++ b/src/components/Tours/ToursList.tsx @@ -1,26 +1,40 @@ import { TourType, ToursListProps } from '@/@types/tours.types'; -import ToursItem from './ToursItem'; import { getTours } from '@api/tours'; import { useInfiniteQuery } from '@tanstack/react-query'; +import React, { useEffect, useState } from 'react'; import InfiniteScroll from 'react-infinite-scroller'; -import React from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import ToursItem from './ToursItem'; +import ToursItemSkeleton from './ToursItemSkeleton'; const ToursList = ({ selectedRegion }: ToursListProps) => { - const { fetchNextPage, hasNextPage, data, error } = useInfiniteQuery({ - queryKey: ['tours', selectedRegion], - queryFn: ({ pageParam = 0 }) => getTours(selectedRegion, pageParam, 10), - initialPageParam: 0, - getNextPageParam: (lastPage) => { - const currentPage = lastPage?.data.data.pageable.pageNumber; - const totalPages = lastPage?.data.data.totalPages; - - if (currentPage < totalPages - 1) { - return currentPage + 1; - } - - return undefined; - }, - }); + const { fetchNextPage, hasNextPage, data, isLoading, error } = + useInfiniteQuery({ + queryKey: ['tours', selectedRegion], + queryFn: ({ pageParam = 0 }) => getTours(selectedRegion, pageParam, 10), + initialPageParam: 0, + getNextPageParam: (lastPage) => { + const currentPage = lastPage?.data.data.pageable.pageNumber; + const totalPages = lastPage?.data.data.totalPages; + + if (currentPage < totalPages - 1) { + return currentPage + 1; + } + + return undefined; + }, + }); + + const [showSkeleton, setShowSkeleton] = useState(isLoading); + + useEffect(() => { + if (isLoading) { + setShowSkeleton(true); + } else { + const timer = setTimeout(() => setShowSkeleton(false), 200); + return () => clearTimeout(timer); + } + }, [isLoading]); if (error) { return
데이터를 불러오는 중 오류가 발생했습니다.
; @@ -32,18 +46,27 @@ const ToursList = ({ selectedRegion }: ToursListProps) => { loadMore={() => fetchNextPage()} hasMore={hasNextPage} loader={ -
- Loading ... +
+
+
Loading...
+
}> -
- {data?.pages.map((group, index) => ( - - {group?.data.data.content.map((tour: TourType) => ( - +
+ {showSkeleton + ? Array.from({ length: 10 }, (_, index) => ( + + )) + : data?.pages.map((group) => ( + + {group?.data.data.content.map((tour: TourType) => ( + + ))} + ))} - - ))}
); diff --git a/src/components/Tours/ToursSectionTop.tsx b/src/components/Tours/ToursSectionTop.tsx index 52786fbc..f1f708e2 100644 --- a/src/components/Tours/ToursSectionTop.tsx +++ b/src/components/Tours/ToursSectionTop.tsx @@ -6,8 +6,8 @@ const ToursSectionTop = () => { const [selectedRegion, setSelectedRegion] = useState('전체'); return ( -
-
+
+

지금 인기여행지

{ - const navigate = useNavigate(); - const location = useLocation(); - - const isActive = (path: string) => location.pathname === path; - - return ( -
- -
- ); -}; - -export default Footer; diff --git a/src/components/common/footer/index.tsx b/src/components/common/footer/index.tsx deleted file mode 100644 index 18e41ffd..00000000 --- a/src/components/common/footer/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import Footer from './Footer'; - -export { Footer }; diff --git a/src/components/common/nav/Nav.tsx b/src/components/common/nav/Nav.tsx index cf531adb..1295f159 100644 --- a/src/components/common/nav/Nav.tsx +++ b/src/components/common/nav/Nav.tsx @@ -8,7 +8,7 @@ const Nav = () => { const isActive = (path: string) => location.pathname === path; return ( -