diff --git a/README.md b/README.md index 25e85dd9..c5e727e7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@
- 오늘의 길 -

나만의 길을 따라 일상을 공유하다 🗺️

- + 오늘의 길
--- diff --git a/frontend/src/api/course/index.ts b/frontend/src/api/course/index.ts index 285a570e..5475475f 100644 --- a/frontend/src/api/course/index.ts +++ b/frontend/src/api/course/index.ts @@ -33,10 +33,11 @@ export const getCourse = async (courseId: number) => { return data; }; -export const getCourseList = async (pageParam: number) => { +export const getCourseList = async (pageParam: number, query?: string) => { const { data } = await axiosInstance.get(END_POINTS.COURSES, { params: { page: pageParam, + query, }, useAuth: false, }); diff --git a/frontend/src/api/map/index.ts b/frontend/src/api/map/index.ts index 33f3a06b..de71e54b 100644 --- a/frontend/src/api/map/index.ts +++ b/frontend/src/api/map/index.ts @@ -30,10 +30,11 @@ export const createMap = async (baseMapData: Omit) => { return data.id; }; -export const getMapList = async (pageParam: number) => { +export const getMapList = async (pageParam: number, query?: string) => { const { data } = await axiosInstance.get(END_POINTS.MAPS, { params: { page: pageParam, + query, }, useAuth: false, }); diff --git a/frontend/src/assets/empty.svg b/frontend/src/assets/empty.svg new file mode 100644 index 00000000..12714f6c --- /dev/null +++ b/frontend/src/assets/empty.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/Banner/BannerSlider.tsx b/frontend/src/components/Banner/BannerSlider.tsx index 2dda8ea8..a0bef5b0 100644 --- a/frontend/src/components/Banner/BannerSlider.tsx +++ b/frontend/src/components/Banner/BannerSlider.tsx @@ -30,6 +30,7 @@ const BannerSlider: React.FC = ({ speed: interval / 10, slidesToShow: 1, slidesToScroll: 1, + autoplay: true, autoplaySpeed: interval, }; @@ -39,6 +40,9 @@ const BannerSlider: React.FC = ({ {banners.map((banner, index) => (
{`Banner window.open(banner.redirectUrl, '_blank')} diff --git a/frontend/src/components/Map/CourseItem.tsx b/frontend/src/components/Map/CourseItem.tsx deleted file mode 100644 index 6413815e..00000000 --- a/frontend/src/components/Map/CourseItem.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { MapItemType } from '@/types'; -import { Link } from 'react-router-dom'; -import PinIcon from '@/components/PinIcon'; -import MapThumbnail from './MapThumbnail'; - -type CourseItemProps = { - courseItem: MapItemType; -}; - -const CourseItem = ({ courseItem }: CourseItemProps) => { - return ( - -
-
- {courseItem.thumbnailUrl.startsWith('https://example') ? ( - - ) : ( - {`${courseItem.title} - )} -
-

{courseItem.title}

-
- -

{courseItem.user.nickname}

- -
-

{courseItem.pinCount}

-
-
-
- - ); -}; - -export default CourseItem; diff --git a/frontend/src/components/Map/CourseListPanel.tsx b/frontend/src/components/Map/CourseListPanel.tsx deleted file mode 100644 index 2b1aefa2..00000000 --- a/frontend/src/components/Map/CourseListPanel.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; - -import { getCourseList } from '@/api/course'; -import { useInfiniteScroll } from '@/hooks/useInfiniteScroll'; -import { CourseList, MapItemType } from '@/types'; - -import CourseItem from './CourseItem'; - -const CourseListPanel = () => { - const infiniteScrollConfig = { - queryKey: ['courseList'], - queryFn: ({ pageParam }: { pageParam: number }) => getCourseList(pageParam), - getNextPageParam: (lastPage: CourseList) => { - return lastPage.currentPage < lastPage.totalPages - ? lastPage.currentPage + 1 - : undefined; - }, - fetchWithoutQuery: true, - }; - - const { data, isFetchingNextPage, hasNextPage, ref } = - useInfiniteScroll(infiniteScrollConfig); - - return ( - <> -
- {data?.pages.map((page, index) => ( - - {page.courses.map((map: MapItemType) => ( - - ))} - - ))} -
-
- - ); -}; - -export default CourseListPanel; diff --git a/frontend/src/components/Map/MapDetailBoard.tsx b/frontend/src/components/Map/MapDetailBoard.tsx index a6a997af..e6418f9f 100644 --- a/frontend/src/components/Map/MapDetailBoard.tsx +++ b/frontend/src/components/Map/MapDetailBoard.tsx @@ -10,7 +10,7 @@ import SideContainer from '@/components/common/SideContainer'; import Marker from '@/components/Marker/Marker'; import DeleteMapButton from './DeleteMapButton'; import EditMapButton from './EditMapButton'; -import MapThumbnail from './MapThumbnail'; +import ListItemThumbnail from '@/components/common/List/ListItemThumbnail'; type MapDetailBoardProps = { mapData: Map; @@ -54,7 +54,7 @@ const MapDetailBoard = ({ mapData }: MapDetailBoardProps) => { )} {mapData.thumbnailUrl.startsWith('https://example') ? ( - + ) : ( )} diff --git a/frontend/src/components/Map/MapItem.tsx b/frontend/src/components/Map/MapItem.tsx deleted file mode 100644 index 32c4258f..00000000 --- a/frontend/src/components/Map/MapItem.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { MapItemType } from '@/types'; -import { Link } from 'react-router-dom'; -import PinIcon from '@/components/PinIcon'; -import MapThumbnail from './MapThumbnail'; - -type MapItemProps = { - mapItem: MapItemType; -}; - -const MapItem = ({ mapItem }: MapItemProps) => { - return ( - -
-
- {mapItem.thumbnailUrl.startsWith('https://example') ? ( - - ) : ( - - )} -
-

{mapItem.title}

-
- -

{mapItem.user.nickname}

- -
-

{mapItem.pinCount}

-
-
-
- - ); -}; - -export default MapItem; diff --git a/frontend/src/components/Map/MapListPanel.tsx b/frontend/src/components/Map/MapListPanel.tsx deleted file mode 100644 index e4e17530..00000000 --- a/frontend/src/components/Map/MapListPanel.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; - -import { getMapList } from '@/api/map'; -import { useInfiniteScroll } from '@/hooks/useInfiniteScroll'; -import { MapItemType, MapList } from '@/types'; - -import MapItem from '@/components/Map/MapItem'; -import Box from '../common/Box'; - -const MapListPanel = () => { - const { data, isFetchingNextPage, hasNextPage, ref } = - useInfiniteScroll({ - queryKey: ['mapList'], - queryFn: ({ pageParam }) => getMapList(pageParam), - getNextPageParam: (lastPage) => { - return lastPage.currentPage < lastPage.totalPages - ? lastPage.currentPage + 1 - : undefined; - }, - fetchWithoutQuery: true, - }); - - return ( - -
- {data?.pages.map((page, index) => ( - - {page.maps.map((map: MapItemType) => ( - - ))} - - ))} -
-
- - ); -}; - -export default MapListPanel; diff --git a/frontend/src/components/common/List/Course/CourseItem.tsx b/frontend/src/components/common/List/Course/CourseItem.tsx new file mode 100644 index 00000000..3b439a74 --- /dev/null +++ b/frontend/src/components/common/List/Course/CourseItem.tsx @@ -0,0 +1,12 @@ +import { MapItemType } from '@/types'; +import ListItem from '@/components/common/List/ListItem'; + +type CourseItemProps = { + courseItem: MapItemType; +}; + +const CourseItem = ({ courseItem }: CourseItemProps) => { + return ; +}; + +export default CourseItem; diff --git a/frontend/src/components/common/List/Course/CourseListPanel.tsx b/frontend/src/components/common/List/Course/CourseListPanel.tsx new file mode 100644 index 00000000..2423688a --- /dev/null +++ b/frontend/src/components/common/List/Course/CourseListPanel.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +import { getCourseList } from '@/api/course'; +import { useInfiniteScroll } from '@/hooks/useInfiniteScroll'; +import { CourseList } from '@/types'; + +import CourseItem from './CourseItem'; +import InfiniteListPanel from '@/components/common/List/InfiniteListPanel'; + +interface CourseListPanelProps { + query?: string; +} + +const CourseListPanel: React.FC = ({ query }) => { + const { data, ref } = useInfiniteScroll({ + queryKey: ['courseList'], + queryFn: ({ pageParam }) => getCourseList(pageParam, query), + getNextPageParam: (lastPage) => + lastPage.currentPage < lastPage.totalPages + ? lastPage.currentPage + 1 + : undefined, + fetchWithoutQuery: true, + }); + + const courseItems = data?.pages.flatMap((page) => page.courses) || []; + + return ( + ( + + )} + className="max-h-[700px] p-5" + /> + ); +}; + +export default CourseListPanel; diff --git a/frontend/src/components/common/List/InfiniteListPanel.tsx b/frontend/src/components/common/List/InfiniteListPanel.tsx new file mode 100644 index 00000000..747fcabd --- /dev/null +++ b/frontend/src/components/common/List/InfiniteListPanel.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import emptyImage from '../../../assets/empty.svg'; + +interface InfiniteListPanelProps { + data: T[] | undefined; + ref: React.Ref; + renderItem: (item: T) => React.ReactNode; + className?: string; +} + +const InfiniteListPanel = ({ + data, + ref, + renderItem, + className, +}: InfiniteListPanelProps) => { + return ( +
+ {data && data.length > 0 ? ( +
+ {data.map((item, index) => ( + {renderItem(item)} + ))} +
+ ) : ( +
+ 리스트가 텅 비었습니다! +
+ )} +
+ ); +}; + +export default InfiniteListPanel; diff --git a/frontend/src/components/common/List/ListItem.tsx b/frontend/src/components/common/List/ListItem.tsx new file mode 100644 index 00000000..05a54d49 --- /dev/null +++ b/frontend/src/components/common/List/ListItem.tsx @@ -0,0 +1,57 @@ +import { Link } from 'react-router-dom'; +import PinIcon from '@/components/PinIcon'; +import React from 'react'; +import ListItemThumbnail from '@/components/common/List/ListItemThumbnail'; + +type ListItemProps = { + item: T; + linkPrefix: string; +}; + +const ListItem = < + T extends { + id: number; + title: string; + thumbnailUrl: string; + user: { profileImageUrl: string; nickname: string }; + pinCount: number; + }, +>({ + item, + linkPrefix, +}: ListItemProps) => { + return ( + +
+
+ {item.thumbnailUrl.startsWith('https://example') ? ( + + ) : ( + {item.title} + )} +
+ +

{item.title}

+ +
+ {item.user.nickname} +

{item.user.nickname}

+ +
+

{item.pinCount}

+
+
+
+ + ); +}; + +export default ListItem; diff --git a/frontend/src/components/Map/MapThumbnail.tsx b/frontend/src/components/common/List/ListItemThumbnail.tsx similarity index 57% rename from frontend/src/components/Map/MapThumbnail.tsx rename to frontend/src/components/common/List/ListItemThumbnail.tsx index 74c82fbd..d5750740 100644 --- a/frontend/src/components/Map/MapThumbnail.tsx +++ b/frontend/src/components/common/List/ListItemThumbnail.tsx @@ -1,17 +1,17 @@ -type MapThumbnailProps = { +type ListItemThumbnailProps = { className?: string; thumbnailUrl?: string; }; -const MapThumbnail = ({ +const ListItemThumbnail = ({ thumbnailUrl = `https://kr.object.ncloudstorage.com/ogil-public/uploads/default_thumbnail/default_3.webp`, className, -}: MapThumbnailProps) => { +}: ListItemThumbnailProps) => { return (
- 지도 썸네일 + 썸네일
); }; -export default MapThumbnail; +export default ListItemThumbnail; diff --git a/frontend/src/components/common/ListToggleButtons.tsx b/frontend/src/components/common/List/ListToggleButtons.tsx similarity index 84% rename from frontend/src/components/common/ListToggleButtons.tsx rename to frontend/src/components/common/List/ListToggleButtons.tsx index ecb69c28..7b379313 100644 --- a/frontend/src/components/common/ListToggleButtons.tsx +++ b/frontend/src/components/common/List/ListToggleButtons.tsx @@ -12,7 +12,7 @@ const ToggleButton: React.FC = ({ onSelect, }) => { return ( -
+
= ({ {options.map((option) => ( diff --git a/frontend/src/pages/HomePage/LogOutButton.tsx b/frontend/src/pages/HomePage/LogOutButton.tsx index c6d76470..7215d583 100644 --- a/frontend/src/pages/HomePage/LogOutButton.tsx +++ b/frontend/src/pages/HomePage/LogOutButton.tsx @@ -8,7 +8,14 @@ const LogOutButton = () => { logoutMutation.mutate(); logout(); }; - return ; + return ( + + ); }; export default LogOutButton; diff --git a/frontend/src/pages/HomePage/LoginButtons.tsx b/frontend/src/pages/HomePage/LoginButtons.tsx index 83d5fc53..648dcd1f 100644 --- a/frontend/src/pages/HomePage/LoginButtons.tsx +++ b/frontend/src/pages/HomePage/LoginButtons.tsx @@ -48,7 +48,12 @@ const LoginButtons = () => { <> {!isLogged ? (
- +
) : ( user && ( diff --git a/frontend/src/pages/HomePage/MainListPanel.tsx b/frontend/src/pages/HomePage/MainListPanel.tsx index 4fb5ed5e..30473cc2 100644 --- a/frontend/src/pages/HomePage/MainListPanel.tsx +++ b/frontend/src/pages/HomePage/MainListPanel.tsx @@ -1,14 +1,14 @@ import React, { useState } from 'react'; -import CourseListPanel from '@/components/Map/CourseListPanel'; -import MapListPanel from '@/components/Map/MapListPanel'; -import ListToggleButtons from '@/components/common/ListToggleButtons'; +import CourseListPanel from '@/components/common/List/Course/CourseListPanel'; +import MapListPanel from '@/components/common/List/Map/MapListPanel'; +import ListToggleButtons from '@/components/common/List/ListToggleButtons'; import { CreateMapType } from '@/types'; const MainListPanel = () => { const [listTab, setListTab] = useState('MAP'); return ( -
+
{ onSelect={(value) => setListTab(value as CreateMapType)} /> -
+
{listTab === 'MAP' ? : }
diff --git a/frontend/src/pages/HomePage/UserProfile.tsx b/frontend/src/pages/HomePage/UserProfile.tsx index de7f54f2..4b2e05a9 100644 --- a/frontend/src/pages/HomePage/UserProfile.tsx +++ b/frontend/src/pages/HomePage/UserProfile.tsx @@ -6,7 +6,7 @@ type UserProfileProps = { const UserProfile = ({ user }: UserProfileProps) => { return ( -
+
{`${user.nickname}님의 = ({ query }) => { + const [listTab, setListTab] = useState('MAP'); + + return ( +
+ setListTab(value as CreateMapType)} + /> + +
+ {listTab === 'MAP' ? ( + + ) : ( + + )} +
+
+ ); +}; + +export default SearchListPanel; diff --git a/frontend/src/pages/SearchPage/SearchPage.tsx b/frontend/src/pages/SearchPage/SearchPage.tsx index 409975f3..9a65495c 100644 --- a/frontend/src/pages/SearchPage/SearchPage.tsx +++ b/frontend/src/pages/SearchPage/SearchPage.tsx @@ -1,10 +1,9 @@ import Footer from '@/pages/HomePage/Footer'; import Header from '@/pages/HomePage/Header'; - -import MainListPanel from '../HomePage/MainListPanel'; import SearchBar from '@/components/common/SearchBar'; import { useSearchParams } from 'react-router-dom'; import { useState } from 'react'; +import SearchListPanel from '@/pages/SearchPage/SearchListPanel'; const SearchPage = () => { const [searchParams] = useSearchParams(); @@ -18,11 +17,15 @@ const SearchPage = () => { return ( <>
-
+
- +