diff --git a/Mine/src/api/magazine.ts b/Mine/src/api/magazine.ts
index b13d6e6..8af9054 100644
--- a/Mine/src/api/magazine.ts
+++ b/Mine/src/api/magazine.ts
@@ -12,6 +12,8 @@ import type {
ResponseAddSectionInSectionPage,
ResponseFeed,
ResponseRecentSection,
+ SearchLikedMagazinePageParams,
+ SearchMagazineFeed,
SectionDetailDto,
} from '../types/magazine'
import type { MyMagazinesDto, ResponseMyMagazine } from '../types/magazine'
@@ -122,3 +124,17 @@ export const patchSection = async ({ magazineId, sectionId, heading, paragraphs
})
return res.data
}
+
+export const getSearchLikedMagazine = async ({ keyword, page = 0, size = 10 }: SearchLikedMagazinePageParams) => {
+ const res = await axiosInstance.get(`api/magazines/liked/search`, {
+ params: { keyword, page, size },
+ })
+ return res.data
+}
+
+export const getSearchMagazineFeed = async ({ keyword, page = 0, size = 10 }: SearchMagazineFeed) => {
+ const res = await axiosInstance.get(`api/magazines/liked/search`, {
+ params: { keyword, page, size },
+ })
+ return res.data
+}
diff --git a/Mine/src/components/skeleton/ExploreSkeleton.tsx b/Mine/src/components/skeleton/ExploreSkeleton.tsx
index 51620bd..6028001 100644
--- a/Mine/src/components/skeleton/ExploreSkeleton.tsx
+++ b/Mine/src/components/skeleton/ExploreSkeleton.tsx
@@ -1,24 +1,21 @@
import { SkeletonBox } from '../../components/skeleton/SkeletonBase'
-import useSidebarStore from '../../stores/sidebar'
export default function ExploreSkeleton() {
- const { isOpen } = useSidebarStore()
return (
-
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
{' '}
)
}
diff --git a/Mine/src/hooks/useGetSearchLikedMagazine.ts b/Mine/src/hooks/useGetSearchLikedMagazine.ts
new file mode 100644
index 0000000..f0a6d3a
--- /dev/null
+++ b/Mine/src/hooks/useGetSearchLikedMagazine.ts
@@ -0,0 +1,25 @@
+import { useInfiniteQuery, type InfiniteData } from '@tanstack/react-query'
+import { getSearchLikedMagazine } from '../api/magazine'
+import type { SearchLikedMagazineResponse } from '../types/magazine'
+import type { AxiosError } from 'axios'
+
+export const useGetSearchLikedMagazines = (keyword: string) => {
+ return useInfiniteQuery<
+ SearchLikedMagazineResponse,
+ AxiosError,
+ InfiniteData,
+ [string, string],
+ number
+ >({
+ queryKey: ['searchLikedMagazines', keyword],
+
+ queryFn: ({ pageParam }) => getSearchLikedMagazine({ keyword, page: pageParam }),
+ initialPageParam: 0,
+ getNextPageParam: (lastPage) => {
+ return lastPage.last ? undefined : lastPage.number + 1
+ },
+
+ // 키워드가 빈 문자열이 아닐 때만 API를 호출하도록 설정 (선택 사항)
+ enabled: !!keyword,
+ })
+}
diff --git a/Mine/src/hooks/useGetSearchMagazineFeed.ts b/Mine/src/hooks/useGetSearchMagazineFeed.ts
new file mode 100644
index 0000000..88737f9
--- /dev/null
+++ b/Mine/src/hooks/useGetSearchMagazineFeed.ts
@@ -0,0 +1,25 @@
+import { useInfiniteQuery, type InfiniteData } from '@tanstack/react-query'
+import { getSearchMagazineFeed } from '../api/magazine'
+import type { SearchMagazineFeedResponse } from '../types/magazine'
+import type { AxiosError } from 'axios'
+
+export const useGetSearchMagazineFeed = (keyword: string) => {
+ return useInfiniteQuery<
+ SearchMagazineFeedResponse,
+ AxiosError,
+ InfiniteData,
+ [string, string],
+ number
+ >({
+ queryKey: ['searchLikedMagazines', keyword],
+
+ queryFn: ({ pageParam }) => getSearchMagazineFeed({ keyword, page: pageParam }),
+ initialPageParam: 0,
+ getNextPageParam: (lastPage) => {
+ return lastPage.last ? undefined : lastPage.number + 1
+ },
+
+ // 키워드가 빈 문자열이 아닐 때만 API를 호출하도록 설정 (선택 사항)
+ enabled: !!keyword,
+ })
+}
diff --git a/Mine/src/pages/magazine/ExplorePage.tsx b/Mine/src/pages/magazine/ExplorePage.tsx
index 681a252..8b04022 100644
--- a/Mine/src/pages/magazine/ExplorePage.tsx
+++ b/Mine/src/pages/magazine/ExplorePage.tsx
@@ -5,21 +5,46 @@ import ExploreSkeleton from '../../components/skeleton/ExploreSkeleton'
import explorebg from '../../assets/explorebg.jpg'
import useSidebarStore from '../../stores/sidebar'
import SearchInput from '../../components/common/SearchInput'
+import { useGetSearchMagazineFeed } from '../../hooks/useGetSearchMagazineFeed'
export default function ExplorePage() {
const { isOpen } = useSidebarStore()
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useGetMagazineFeed()
const [searchValue, setSearchValue] = useState('')
+ const {
+ data: magazinedata,
+ fetchNextPage: magazineFetchNextPage,
+ hasNextPage: magazineHasNextPage,
+ isFetchingNextPage: magazineIsFetchingNextPage,
+ isLoading: magazineIsLoading,
+ } = useGetMagazineFeed()
+ const {
+ data: searchData,
+ fetchNextPage: searchFetchNextPage,
+ hasNextPage: searchHasNextPage,
+ isFetchingNextPage: searchIsFetchingNextPage,
+ isLoading: searchIsLoading,
+ } = useGetSearchMagazineFeed(searchValue)
+
const observerRef = useRef(null)
- const magazines = data?.pages.flatMap((page) => page.content) ?? []
+ const isSearching = searchValue.trim() !== ''
+
+ const defaultMagazines = magazinedata?.pages.flatMap((page) => page.content) ?? []
+ const searchMagazines = searchData?.pages.flatMap((page) => page.content) ?? []
+
+ const activeMagazines = isSearching ? searchMagazines : defaultMagazines
+
+ const activeHasNextPage = isSearching ? searchHasNextPage : magazineHasNextPage
+ const activeIsFetchingNextPage = isSearching ? searchIsFetchingNextPage : magazineIsFetchingNextPage
+ const activeFetchNextPage = isSearching ? searchFetchNextPage : magazineFetchNextPage
+ const activeIsLoading = isSearching ? searchIsLoading : magazineIsLoading
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
- if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
- fetchNextPage()
+ if (entries[0].isIntersecting && activeHasNextPage && !activeIsFetchingNextPage) {
+ activeFetchNextPage()
}
},
{ threshold: 0.1 }
@@ -27,9 +52,9 @@ export default function ExplorePage() {
if (observerRef.current) observer.observe(observerRef.current)
return () => observer.disconnect()
- }, [hasNextPage, isFetchingNextPage, fetchNextPage])
+ }, [activeHasNextPage, activeIsFetchingNextPage, activeFetchNextPage])
- if (isLoading) return
+ // if (activeIsLoading) return
return (
-
+
-
-
- {isFetchingNextPage &&
로딩중...
}
+ {!activeIsLoading && (
+ <>
+
+
+ {magazineIsFetchingNextPage &&
로딩중...
}
+ >
+ )}
+ {activeIsLoading &&
}
diff --git a/Mine/src/pages/magazine/SavedMagazinePage.tsx b/Mine/src/pages/magazine/SavedMagazinePage.tsx
index 3e461db..6984523 100644
--- a/Mine/src/pages/magazine/SavedMagazinePage.tsx
+++ b/Mine/src/pages/magazine/SavedMagazinePage.tsx
@@ -9,6 +9,7 @@ import useSidebarStore from '../../stores/sidebar'
import SearchInput from '../../components/common/SearchInput'
import MineLogo from '../../icon/logo_with_title.svg?react'
import type { Magazine } from '../../types/magazine'
+import { useGetSearchLikedMagazines } from '../../hooks/useGetSearchLikedMagazine'
const CARD_WIDTH = 476
const GAP = 16
@@ -38,18 +39,30 @@ export default function SavedMagazinePage() {
const [columnIndex, setColumnIndex] = useState(0)
const { isOpen } = useSidebarStore()
const [searchValue, setSearchValue] = useState('')
+ const [isAnimating, setIsAnimating] = useState(true)
const navigate = useNavigate()
- const { data, isLoading, isError } = useGetLikedMagazineList({
+ const {
+ data: baseData,
+ isLoading: isBaseLoading,
+ isError,
+ } = useGetLikedMagazineList({
page: 0,
size: 20,
sort: ['createdAt,desc'],
})
- if (isLoading) return
+ const { data: searchData, fetchNextPage, hasNextPage } = useGetSearchLikedMagazines(searchValue)
+
+ const baseMagazines = baseData?.content ?? []
+ const searchMagazines = searchData?.pages.flatMap((page) => page.content) ?? []
+
+ const isSearching = searchValue.trim() != ''
+ const magazines = isSearching ? searchMagazines : baseMagazines
+
+ if (isBaseLoading) return
if (isError) return 불러오기 실패
- const magazines = data?.content ?? []
const isEmpty = magazines.length === 0
const totalSlots = Math.max(magazines.length, MIN_CARDS)
@@ -62,6 +75,27 @@ export default function SavedMagazinePage() {
const TOTAL_PAGES = Math.ceil(TOTAL_COLUMNS / COLUMNS_PER_VIEW)
const firstEmptyIndex = allItems.findIndex((item) => item === null)
+ const handleSearchChange = (value: string) => {
+ setIsAnimating(false)
+ setSearchValue(value)
+ setColumnIndex(0)
+ }
+ const handleNextClick = () => {
+ setIsAnimating(true)
+ const nextIndex = Math.min(columnIndex + 1, TOTAL_PAGES - 1)
+ setColumnIndex(nextIndex)
+
+ if (isSearching && hasNextPage) {
+ if (nextIndex >= TOTAL_PAGES - 2) {
+ fetchNextPage()
+ }
+ }
+ }
+ const handlePrevClick = () => {
+ setIsAnimating(true)
+ setColumnIndex((prev) => Math.max(prev - 1, 0))
+ }
+
if (isEmpty) {
return (
-
+
@@ -122,15 +156,15 @@ export default function SavedMagazinePage() {
>
-
+
{columns.map((col, colIdx) => (
@@ -155,8 +189,8 @@ export default function SavedMagazinePage() {
setColumnIndex((prev) => Math.min(prev + 1, TOTAL_PAGES - 1))}
- onPrev={() => setColumnIndex((prev) => Math.max(prev - 1, 0))}
+ onNext={handleNextClick}
+ onPrev={handlePrevClick}
/>
diff --git a/Mine/src/types/magazine.ts b/Mine/src/types/magazine.ts
index 17ed1f9..d6c19e4 100644
--- a/Mine/src/types/magazine.ts
+++ b/Mine/src/types/magazine.ts
@@ -169,3 +169,40 @@ export type PatchSectionDto = {
imageUrl: string
}[]
}
+
+type SearchBase = {
+ keyword: string
+ page?: number
+ size?: number
+}
+
+export type SearchLikedMagazinePageParams = SearchBase
+
+export type SearchMagazineFeed = SearchBase
+
+type Content = {
+ title: string
+ coverImageUrl: string
+ username: string
+ likeCount: number
+ commentCount: number
+ createdAt: string
+ moodboard: Moodboard
+ magazineId: number
+}
+
+type SearchResponse = {
+ content: Content[]
+ totalPages: number
+ totalElements: number
+ last: boolean
+ size: number
+ number: number
+ numberOfElements: number
+ first: boolean
+ empty: boolean
+}
+
+export type SearchLikedMagazineResponse = SearchResponse
+
+export type SearchMagazineFeedResponse = SearchResponse