Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Mine/src/api/magazine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import type {
ResponseAddSectionInSectionPage,
ResponseFeed,
ResponseRecentSection,
SearchLikedMagazinePageParams,
SearchMagazineFeed,
SectionDetailDto,
} from '../types/magazine'
import type { MyMagazinesDto, ResponseMyMagazine } from '../types/magazine'
Expand Down Expand Up @@ -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
}
Comment thread
drddyn marked this conversation as resolved.
25 changes: 11 additions & 14 deletions Mine/src/components/skeleton/ExploreSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import { SkeletonBox } from '../../components/skeleton/SkeletonBase'
import useSidebarStore from '../../stores/sidebar'

export default function ExploreSkeleton() {
const { isOpen } = useSidebarStore()
return (
<div className="flex w-full h-screen overflow-y-hidden pt-40.25">
<div className={`flex transition-all duration-300 ${isOpen ? 'w-60' : 'w-15'}`} />
<div className="flex w-full overflow-y-hidden">
<div className="relative flex flex-1 justify-center overflow-hidden ">
<div className="grid grid-cols-3 h-fit gap-2 shrink-0">
<SkeletonBox className="w-90 h-60" />
<SkeletonBox className="w-90 h-60" />
<SkeletonBox className="w-90 h-60" />
<SkeletonBox className="w-90 h-60" />
<SkeletonBox className="w-90 h-60" />
<SkeletonBox className="w-90 h-60" />
<SkeletonBox className="w-90 h-60" />
<SkeletonBox className="w-90 h-60" />
<SkeletonBox className="w-90 h-60" />
<SkeletonBox className="w-90.5 h-60" />
<SkeletonBox className="w-90.5 h-60" />
<SkeletonBox className="w-90.5 h-60" />
<SkeletonBox className="w-90.5 h-60" />
<SkeletonBox className="w-90.5 h-60" />
<SkeletonBox className="w-90.5 h-60" />
<SkeletonBox className="w-90.5 h-60" />
<SkeletonBox className="w-90.5 h-60" />
<SkeletonBox className="w-90.5 h-60" />
</div>
</div>
</div>{' '}
</div>
)
}
25 changes: 25 additions & 0 deletions Mine/src/hooks/useGetSearchLikedMagazine.ts
Original file line number Diff line number Diff line change
@@ -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<SearchLikedMagazineResponse>,
[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,
})
}
25 changes: 25 additions & 0 deletions Mine/src/hooks/useGetSearchMagazineFeed.ts
Original file line number Diff line number Diff line change
@@ -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<SearchMagazineFeedResponse>,
[string, string],
number
>({
queryKey: ['searchLikedMagazines', keyword],
Comment thread
drddyn marked this conversation as resolved.

queryFn: ({ pageParam }) => getSearchMagazineFeed({ keyword, page: pageParam }),
initialPageParam: 0,
getNextPageParam: (lastPage) => {
return lastPage.last ? undefined : lastPage.number + 1
},

// 키워드가 빈 문자열이 아닐 때만 API를 호출하도록 설정 (선택 사항)
enabled: !!keyword,
})
}
50 changes: 40 additions & 10 deletions Mine/src/pages/magazine/ExplorePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,56 @@ 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<HTMLDivElement>(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 }
)

if (observerRef.current) observer.observe(observerRef.current)
return () => observer.disconnect()
}, [hasNextPage, isFetchingNextPage, fetchNextPage])
}, [activeHasNextPage, activeIsFetchingNextPage, activeFetchNextPage])

if (isLoading) return <ExploreSkeleton />
// if (activeIsLoading) return <ExploreSkeleton />
Comment thread
drddyn marked this conversation as resolved.

return (
<div
Expand All @@ -39,7 +64,7 @@ export default function ExplorePage() {
backgroundAttachment: 'fixed',
}}
>
<div className="absolute inset-0 bg-gray-600-op30 pointer-events-none" />
<div className="fixed inset-0 bg-gray-600-op30 pointer-events-none" />

<div
className="fixed top-0 left-0 w-full pointer-events-none z-10"
Expand All @@ -56,9 +81,14 @@ export default function ExplorePage() {

<div className="relative z-20 flex justify-center ">
<div className={`transition-all duration-200 ${isOpen ? 'ml-60' : 'ml-15'}`}>
<ExploreGrid magazines={magazines} />
<div ref={observerRef} className="h-10" />
{isFetchingNextPage && <div className="text-center py-4">로딩중...</div>}
{!activeIsLoading && (
<>
<ExploreGrid magazines={activeMagazines} />
<div ref={observerRef} className="h-10" />
{magazineIsFetchingNextPage && <div className="text-center py-4">로딩중...</div>}
Comment thread
drddyn marked this conversation as resolved.
</>
)}
{activeIsLoading && <ExploreSkeleton />}
</div>
</div>
</div>
Expand Down
52 changes: 43 additions & 9 deletions Mine/src/pages/magazine/SavedMagazinePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <SavedMagazineSkeleton />
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 <SavedMagazineSkeleton />
Comment thread
drddyn marked this conversation as resolved.
if (isError) return <div>불러오기 실패</div>

const magazines = data?.content ?? []
const isEmpty = magazines.length === 0

const totalSlots = Math.max(magazines.length, MIN_CARDS)
Expand All @@ -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 (
<div
Expand All @@ -74,7 +108,7 @@ export default function SavedMagazinePage() {
<div className="absolute inset-0 bg-gray-600-op30 pointer-events-none" />

<div className="fixed top-8.75 right-4.75 z-30">
<SearchInput value={searchValue} onChange={setSearchValue} />
<SearchInput value={searchValue} onChange={handleSearchChange} />
</div>

<div className="relative flex flex-col justify-center items-center w-full h-screen overflow-hidden">
Expand Down Expand Up @@ -122,15 +156,15 @@ export default function SavedMagazinePage() {
>
<div className="absolute inset-0 bg-gray-600-op30 pointer-events-none" />
<div className="fixed top-8.75 right-4.75 z-30">
<SearchInput value={searchValue} onChange={setSearchValue} />
<SearchInput value={searchValue} onChange={handleSearchChange} />
</div>
<div
className={`relative flex flex-col justify-center h-full overflow-hidden transition-all duration-300 ${isOpen ? 'pl-76.5' : 'pl-66.5'}`}
>
<div className="relative flex items-center overflow-visible w-full">
<div
className="flex gap-2 transition-transform duration-500 ease-in-out will-change-transform"
style={{ transform: `translateX(-${columnIndex * COLUMN_STEP}px)` }}
className={`flex gap-2 will-change-transform ${isAnimating ? 'transition-transform duration-500 ease-in-out' : ''} `}
style={{ transform: isAnimating ? `translateX(-${columnIndex * COLUMN_STEP}px)` : 'none' }}
>
{columns.map((col, colIdx) => (
<div key={colIdx} className="flex flex-col gap-2 shrink-0">
Expand All @@ -155,8 +189,8 @@ export default function SavedMagazinePage() {
<ArrowPagination
currentPage={Math.min(columnIndex + 1, TOTAL_PAGES)}
totalPages={TOTAL_PAGES}
onNext={() => setColumnIndex((prev) => Math.min(prev + 1, TOTAL_PAGES - 1))}
onPrev={() => setColumnIndex((prev) => Math.max(prev - 1, 0))}
onNext={handleNextClick}
onPrev={handlePrevClick}
/>
</div>
</div>
Expand Down
37 changes: 37 additions & 0 deletions Mine/src/types/magazine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading