Skip to content

Commit 1a9415e

Browse files
authored
Merge pull request #207 from dnd-side-project/feat/#194/feed-cd-api
[feat] ํ”ผ๋“œ CD ์žฌ์ƒ API ์—ฐ๊ฒฐ
2 parents 7290a49 + 4a8aa9b commit 1a9415e

File tree

18 files changed

+347
-391
lines changed

18 files changed

+347
-391
lines changed

โ€Žsrc/entities/playlist/api/playlist.tsโ€Ž

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import type {
55
PlaylistDetail,
66
PlaylistParams,
77
PlaylistResponse,
8+
CarouselParams,
9+
CarouselCdListResponse,
810
} from '@/entities/playlist/types/playlist'
911
import { api } from '@/shared/api/httpClient'
1012

@@ -72,3 +74,17 @@ export const postPlaylistConfirm = (playlistId: number) => {
7274
export const getPlaylistViewCounts = (playlistId: number) => {
7375
return api.get(`/main/playlist/browse/view-counts/${playlistId}`)
7476
}
77+
78+
// ํ”ผ๋“œ ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ ์บ๋Ÿฌ์…€ ์กฐํšŒ
79+
export const getCdCarousel = (shareCode: string, params: CarouselParams) => {
80+
return api.get<CarouselCdListResponse>(`/main/playlist/feed/${shareCode}/carousel`, {
81+
params,
82+
})
83+
}
84+
85+
// ํ”ผ๋“œ ์ข‹์•„์š”ํ•œ ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ ์บ๋Ÿฌ์…€ ์กฐํšŒ
86+
export const getLikedCdCarousel = (shareCode: string, params: CarouselParams) => {
87+
return api.get<CarouselCdListResponse>(`/main/playlist/feed/${shareCode}/likes/carousel`, {
88+
params,
89+
})
90+
}

โ€Žsrc/entities/playlist/model/useMyCd.tsโ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export const useMyCdActions = (cdId: number, options?: { enabled?: boolean }) =>
5353
mutationKey: ['patchMyCdPublic', cdId],
5454
mutationFn: () => patchMyCdPublic(cdId),
5555
onSuccess: () => {
56+
queryClient.invalidateQueries({ queryKey: ['playlistDetail', cdId] })
5657
queryClient.invalidateQueries({ queryKey: ['getTracklist', cdId] })
5758
queryClient.invalidateQueries({ queryKey: ['myCdList'] })
5859
queryClient.invalidateQueries({ queryKey: ['feedCdList'] })

โ€Žsrc/entities/playlist/model/usePlaylists.tsโ€Ž

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
} from '@tanstack/react-query'
1111

1212
import {
13+
getCdCarousel,
1314
getCdList,
15+
getLikedCdCarousel,
1416
getLikedCdList,
1517
getMyPlaylistDetail,
1618
getPlaylistDetail,
@@ -24,6 +26,8 @@ import type {
2426
PlaylistResponse,
2527
CdListParams,
2628
FEED_CD_LIST_TAB_TYPE,
29+
CarouselParams,
30+
CarouselDirection,
2731
} from '@/entities/playlist/types/playlist'
2832
import { useAuthStore, type ShareCode } from '@/features/auth'
2933

@@ -146,3 +150,39 @@ export const useFeedCdList = ({
146150
enabled: !!shareCode,
147151
})
148152
}
153+
154+
type PageParam = { cursor: number; direction: CarouselDirection } | undefined
155+
156+
export const useCarouselCdList = (
157+
type: FEED_CD_LIST_TAB_TYPE, // cds or likes
158+
shareCode: string,
159+
params: CarouselParams
160+
) => {
161+
return useInfiniteQuery({
162+
queryKey: ['feedCdList', type, shareCode, params.sort, params.anchorId],
163+
164+
queryFn: ({ pageParam }: { pageParam: PageParam }) => {
165+
const fetchFn = type === 'cds' ? getCdCarousel : getLikedCdCarousel
166+
167+
return fetchFn(shareCode, {
168+
...params,
169+
cursor: pageParam?.cursor,
170+
direction: pageParam?.direction,
171+
})
172+
},
173+
174+
initialPageParam: undefined as PageParam,
175+
176+
getNextPageParam: (lastPage): PageParam => {
177+
if (!lastPage.hasNext || lastPage.nextCursor === null) return undefined
178+
return { cursor: lastPage.nextCursor, direction: 'NEXT' }
179+
},
180+
181+
getPreviousPageParam: (firstPage): PageParam => {
182+
if (!firstPage.hasPrev || firstPage.prevCursor === null) return undefined
183+
return { cursor: firstPage.prevCursor, direction: 'PREV' }
184+
},
185+
186+
enabled: !!shareCode,
187+
})
188+
}

โ€Žsrc/entities/playlist/types/playlist.tsโ€Ž

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,24 @@ export interface PlaylistParams {
9595
cursorId?: number
9696
size?: number
9797
}
98+
99+
export type CarouselDirection = 'NEXT' | 'PREV'
100+
export type CarouselSort = 'POPULAR' | 'RECENT'
101+
102+
export interface CarouselParams {
103+
anchorId?: number
104+
direction?: CarouselDirection
105+
cursor?: number
106+
sort?: CarouselSort
107+
limit?: number
108+
}
109+
110+
export interface CarouselCdListResponse {
111+
content: (CdBasicInfo & OnlyCdResponse)[]
112+
prevCursor: number | null
113+
nextCursor: number | null
114+
size: number
115+
hasPrev: boolean
116+
hasNext: boolean
117+
totalCount: number
118+
}
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
import { Outlet } from 'react-router-dom'
1+
import { Outlet, useParams } from 'react-router-dom'
2+
3+
import { useOwnerStatus } from '@/features/auth'
4+
import { Loading } from '@/shared/ui'
25

36
const FeedLayout = () => {
4-
return <Outlet />
7+
const { shareCode = '' } = useParams()
8+
9+
const { data, isLoading } = useOwnerStatus(shareCode || '')
10+
11+
if (isLoading) return <Loading isLoading />
12+
13+
return <Outlet context={{ isOwner: data?.isOwner }} />
514
}
615

716
export default FeedLayout
Lines changed: 2 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,5 @@
1-
import { useEffect, useCallback, useState, useMemo } from 'react'
2-
import { useNavigate, useParams } from 'react-router-dom'
1+
import { FeedCarousel } from '@/pages/feed/ui'
32

4-
import { useMyCdActions, useMyCdList } from '@/entities/playlist/model/useMyCd'
5-
import { CdViewerLayout } from '@/pages/feed/ui'
6-
import { Loading } from '@/shared/ui'
7-
8-
const Cds = () => {
9-
const navigate = useNavigate()
10-
const { id: routePlaylistId } = useParams<{ id?: string }>()
11-
12-
const myCdPlaylist = useMyCdList('RECENT')
13-
14-
const playlistData = useMemo(() => {
15-
return myCdPlaylist.data ?? []
16-
}, [myCdPlaylist.data])
17-
18-
const isLoading = myCdPlaylist.isLoading
19-
const isError = myCdPlaylist.isError
20-
21-
const [centerItem, setCenterItem] = useState<{
22-
playlistId: number | null
23-
playlistName: string
24-
}>({ playlistId: null, playlistName: '' })
25-
26-
useEffect(() => {
27-
if (isLoading || !playlistData.length) return
28-
29-
const routeId = routePlaylistId ? Number(routePlaylistId) : null
30-
const found = routeId ? playlistData.find((p) => p.playlistId === routeId) : null
31-
32-
if (found) {
33-
setCenterItem({
34-
playlistId: found.playlistId,
35-
playlistName: found.playlistName,
36-
})
37-
} else {
38-
const first = playlistData[0]
39-
40-
setCenterItem({
41-
playlistId: first.playlistId,
42-
playlistName: first.playlistName,
43-
})
44-
45-
navigate(`./${first.playlistId}`, { replace: true })
46-
}
47-
}, [playlistData, isLoading, routePlaylistId, navigate])
48-
49-
const handleCenterChange = useCallback(
50-
(playlist: { playlistId: number; playlistName: string }) => {
51-
setCenterItem(playlist)
52-
53-
const path = `./${playlist.playlistId}`
54-
55-
navigate(path, { replace: true })
56-
},
57-
[navigate]
58-
)
59-
60-
const myCdDetail = useMyCdActions(Number(centerItem.playlistId), {
61-
enabled: !!centerItem.playlistId,
62-
})
63-
64-
const playlistDetail = myCdDetail.tracklist
65-
66-
if (isLoading) return <Loading isLoading />
67-
68-
if (isError) {
69-
navigate('/error')
70-
return null
71-
}
72-
73-
return (
74-
<>
75-
<CdViewerLayout
76-
playlistData={playlistData}
77-
playlistDetail={playlistDetail}
78-
centerItem={centerItem}
79-
onCenterChange={handleCenterChange}
80-
pageType="MY"
81-
isOwner // TODO: ์‹ค์ œ ๊ฐ’์œผ๋กœ ์ˆ˜์ • ํ•„์š”
82-
/>
83-
</>
84-
)
85-
}
3+
const Cds = () => <FeedCarousel type="cds" pageType="MY" />
864

875
export default Cds

โ€Žsrc/pages/feed/index.tsxโ€Ž

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { useMemo, useState } from 'react'
2-
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
2+
import { useNavigate, useOutletContext, useParams, useSearchParams } from 'react-router-dom'
33

44
import styled, { css } from 'styled-components'
55

66
import { Gear } from '@/assets/icons'
77
import { FeedBg } from '@/assets/images'
88
import { type FEED_CD_LIST_TAB_TYPE } from '@/entities/playlist'
99
import { useUserProfile } from '@/entities/user'
10-
import { useOwnerStatus } from '@/features/auth'
10+
import { type ShareCodeOwnerResponse } from '@/features/auth'
1111
import { FeedCdList, FeedProfile } from '@/pages/feed/ui'
1212
import { FeedbackIcon } from '@/pages/feedback/ui'
1313
import { flexRowCenter } from '@/shared/styles/mixins'
@@ -26,7 +26,7 @@ const FeedPage = () => {
2626
const [searchParams, setSearchParams] = useSearchParams()
2727
const navigate = useNavigate()
2828

29-
const { data: ownershipData, isLoading: isOwnershipLoading } = useOwnerStatus(shareCode || '')
29+
const { isOwner: isMyFeed } = useOutletContext<ShareCodeOwnerResponse>()
3030
const {
3131
userProfile,
3232
isLoading: isProfileLoading,
@@ -49,7 +49,6 @@ const FeedPage = () => {
4949
},
5050
})
5151

52-
const isMyFeed = ownershipData?.isOwner ?? false
5352
const currentTab = (searchParams.get('tab') || 'cds') as FEED_CD_LIST_TAB_TYPE
5453

5554
const TAB_LIST = useMemo(
@@ -74,8 +73,9 @@ const FeedPage = () => {
7473
setSearchParams({ tab: nextTab }, { replace: true })
7574
}
7675

77-
if ((isOwnershipLoading && !ownershipData) || isProfileLoading) return <Loading isLoading />
76+
if (isProfileLoading) return <Loading isLoading />
7877
if (isProfileError) return <NotFound isFullPage isProfile />
78+
7979
return (
8080
<FeedWrapper>
8181
<TopVisualBackground>
Lines changed: 2 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,5 @@
1-
import { useEffect, useCallback, useState, useMemo, useRef } from 'react'
2-
import { useNavigate, useParams } from 'react-router-dom'
1+
import { FeedCarousel } from '@/pages/feed/ui'
32

4-
import { usePlaylistDetail } from '@/entities/playlist'
5-
import { useMyLikedCdList } from '@/entities/playlist/model/useMyCd'
6-
import { CdViewerLayout } from '@/pages/feed/ui'
7-
import { Loading } from '@/shared/ui'
8-
9-
const Likes = () => {
10-
const navigate = useNavigate()
11-
const { id: routePlaylistId } = useParams<{ id?: string }>()
12-
13-
const likedCdPlaylist = useMyLikedCdList('RECENT')
14-
15-
const playlistData = useMemo(() => {
16-
return likedCdPlaylist.data ?? []
17-
}, [likedCdPlaylist.data])
18-
19-
const isLoading = likedCdPlaylist.isLoading
20-
const isError = likedCdPlaylist.isError
21-
22-
const [centerItem, setCenterItem] = useState<{
23-
playlistId: number | null
24-
playlistName: string
25-
}>({ playlistId: null, playlistName: '' })
26-
27-
const lastIndexRef = useRef<number>(0)
28-
useEffect(() => {
29-
if (likedCdPlaylist.isLoading || !playlistData.length) return
30-
31-
const routeId = routePlaylistId ? Number(routePlaylistId) : null
32-
const currentIndex = playlistData.findIndex((p) => p.playlistId === routeId)
33-
34-
if (currentIndex !== -1) {
35-
lastIndexRef.current = currentIndex
36-
setCenterItem({
37-
playlistId: playlistData[currentIndex].playlistId,
38-
playlistName: playlistData[currentIndex].playlistName,
39-
})
40-
} else {
41-
const nextItem = playlistData[lastIndexRef.current] || playlistData.at(-1)
42-
43-
if (nextItem) {
44-
setCenterItem({
45-
playlistId: nextItem.playlistId,
46-
playlistName: nextItem.playlistName,
47-
})
48-
49-
navigate(`../${nextItem.playlistId}`, { replace: true })
50-
}
51-
}
52-
}, [playlistData, likedCdPlaylist.isLoading, routePlaylistId, navigate])
53-
54-
const handleCenterChange = useCallback(
55-
(playlist: { playlistId: number; playlistName: string }) => {
56-
setCenterItem(playlist)
57-
58-
const path = `../${playlist.playlistId}`
59-
60-
navigate(path, { replace: true })
61-
},
62-
[navigate]
63-
)
64-
65-
const likedCdDetail = usePlaylistDetail(centerItem.playlistId, {
66-
enabled: !!centerItem.playlistId,
67-
})
68-
const playlistDetail = likedCdDetail.data
69-
70-
if (isLoading) return <Loading isLoading />
71-
72-
if (isError) {
73-
navigate('/error')
74-
return null
75-
}
76-
77-
return (
78-
<>
79-
<CdViewerLayout
80-
playlistData={playlistData}
81-
playlistDetail={playlistDetail}
82-
centerItem={centerItem}
83-
onCenterChange={handleCenterChange}
84-
pageType="LIKE"
85-
isOwner // TODO: ์‹ค์ œ ๊ฐ’์œผ๋กœ ์ˆ˜์ • ํ•„์š”
86-
/>
87-
</>
88-
)
89-
}
3+
const Likes = () => <FeedCarousel type="likes" pageType="LIKE" />
904

915
export default Likes

0 commit comments

Comments
ย (0)