Skip to content

[feat] 피드 CD 재생 API 연결#207

Merged
maylh merged 10 commits intodevelopfrom
feat/#194/feed-cd-api
Mar 12, 2026
Merged

[feat] 피드 CD 재생 API 연결#207
maylh merged 10 commits intodevelopfrom
feat/#194/feed-cd-api

Conversation

@maylh
Copy link
Collaborator

@maylh maylh commented Mar 12, 2026

🛰️ 관련 이슈


✨ 주요 변경 사항

피드 CD 재생 API 연결


🗯️ PR 포인트

  • isOwner 판단 로직을 FeedLayout에서 담당하도록 변경
    feed 하위 여러 라우트에서 동일하게 사용하는 isOwner 판단 로직을 중복 없이 관리하기 위해 공통 부모인 FeedLayout에서 계산하고 Outlet context로 전달하도록 구조를 변경했습니다. 일부 라우트에서 사용하지 않더라도 공통 로직을 상위 레벨에 두는 것이 유지보수성과 상태 일관성 측면에서 더 적합하다고 판단했습니다.

  • 테스트 시 좋아요 해제 및 내 플리 삭제하는 과정에서 데이터 갱신과 관련된 이슈가 있습니다. 서버 이슈로 판단되어서 현재 수정 요청 드린 상태입니다 ! (디코 참고) 수정 후에도 동일 이슈 계속해서 발생하면 QA 진행하면서 추가로 확인해보겠습니다


🚀 알게된 점


📖 참고 자료 (선택)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 피드 플레이리스트용 캐러셀용 서버 페이지네이션 엔드포인트 추가
    • 캐러셀용 좌/우(이전/다음) 페이징 및 커서 기반 탐색 지원
  • 개선 사항

    • 피드 레이아웃과 캐러셀 컴포넌트 단순화로 로딩/탐색 개선
    • 최근순/인기순 정렬 및 앵커 기반 이동 지원
    • CD 회전 애니메이션 및 재생 표시 개선, 좋아요 버튼 항상 표시됨

@maylh maylh added this to the 2차 스프린트 개발 milestone Mar 12, 2026
@maylh maylh self-assigned this Mar 12, 2026
@maylh maylh requested a review from hansololiviakim as a code owner March 12, 2026 02:20
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 피드 페이지의 CD 재생 기능을 위한 API 연결을 구현하고, 관련 컴포넌트 구조를 리팩토링하여 코드의 재사용성과 유지보수성을 높이는 데 중점을 둡니다. 특히, isOwner 판단 로직을 상위 컴포넌트에서 관리하도록 변경하고, CD 캐러셀의 데이터 로딩 및 뷰 로직을 분리하여 더욱 견고하고 확장 가능한 아키텍처를 구축했습니다. 이를 통해 피드 페이지에서 CD 플레이리스트를 효율적으로 탐색하고 재생할 수 있게 됩니다.

Highlights

  • 피드 CD 재생 API 연결: 피드 페이지에서 CD 캐러셀 데이터를 가져오고 관리하기 위한 새로운 API 엔드포인트와 데이터 모델이 추가되었습니다.
  • isOwner 로직 중앙화: FeedLayout 컴포넌트에서 isOwner 상태를 판단하고 하위 라우트에 Outlet 컨텍스트를 통해 전달하도록 변경하여 중복 로직을 제거하고 유지보수성을 향상시켰습니다.
  • CD 캐러셀 컴포넌트 리팩토링: CdCarousel 컴포넌트가 CarouselItem으로 이름이 변경되고, 캐러셀 데이터 로딩 및 상태 관리를 담당하는 FeedCarousel 컴포넌트가 새로 추가되어 책임이 분리되었습니다.
  • 무한 스크롤 캐러셀 구현: useInfiniteQuery를 활용하여 피드 CD 캐러셀에 무한 스크롤 기능을 구현하여 사용자 경험을 개선했습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/entities/playlist/api/playlist.ts
    • 캐러셀 관련 타입(CarouselParams, CarouselCdListResponse)을 추가하고, 피드 CD 캐러셀 조회(getCdCarousel) 및 좋아요한 CD 캐러셀 조회(getLikedCdCarousel) API 함수를 추가했습니다.
  • src/entities/playlist/model/useMyCd.ts
    • patchMyCdPublic 성공 시 playlistDetail 쿼리를 무효화하도록 로직을 추가했습니다.
  • src/entities/playlist/model/usePlaylists.ts
    • 캐러셀 API 함수를 임포트하고, useInfiniteQuery를 활용한 useCarouselCdList 훅을 추가하여 무한 스크롤 캐러셀 데이터를 관리하도록 했습니다.
  • src/entities/playlist/types/playlist.ts
    • 캐러셀 방향(CarouselDirection), 정렬(CarouselSort), 파라미터(CarouselParams), 응답(CarouselCdListResponse)에 대한 새로운 타입 정의를 추가했습니다.
  • src/pages/feed/FeedLayout.tsx
    • useOwnerStatus 훅을 사용하여 shareCode의 소유자 여부를 확인하고, 이 isOwner 값을 Outlet 컨텍스트를 통해 하위 컴포넌트에 전달하도록 변경했습니다.
  • src/pages/feed/cds/index.tsx
    • 기존의 CD 목록 로직을 제거하고 새로 추가된 FeedCarousel 컴포넌트를 사용하여 "나의 CD" 캐러셀을 렌더링하도록 변경했습니다.
  • src/pages/feed/index.tsx
    • useOwnerStatus 훅 사용을 제거하고, useOutletContext를 통해 FeedLayout에서 전달받은 isOwner 값을 사용하도록 변경했습니다.
  • src/pages/feed/likes/index.tsx
    • 기존의 좋아요한 CD 목록 로직을 제거하고 새로 추가된 FeedCarousel 컴포넌트를 사용하여 "좋아요한 CD" 캐러셀을 렌더링하도록 변경했습니다.
  • src/pages/feed/ui/CdCarousel.tsx
    • 이 파일은 삭제되었습니다. 캐러셀 로직은 FeedCarousel로, UI는 CarouselItem으로 분리되었습니다.
  • src/pages/feed/ui/FeedCarousel.tsx
    • 피드 CD 캐러셀의 데이터 로딩, 무한 스크롤, 상태 관리 등을 담당하는 새로운 컴포넌트 FeedCarousel을 추가했습니다.
  • src/pages/feed/ui/FeedCdList.tsx
    • CD 목록 아이템 클릭 시 navigate 함수의 state에 currentSort 값을 추가하여 정렬 상태를 유지하도록 했습니다.
  • src/pages/feed/ui/index.ts
    • CdCarousel을 제거하고 CarouselItem과 FeedCarousel을 새로 내보내도록 업데이트했습니다.
  • src/pages/feed/ui/layout/CdPlayerLayout.tsx
    • Outlet 컴포넌트에 isOwner 컨텍스트를 전달하도록 변경했습니다.
  • src/pages/feed/ui/layout/CdViewerLayout.tsx
    • src/pages/feed/ui/CarouselItem.tsx로 파일 이름을 변경하고, 캐러셀의 개별 아이템을 표시하는 UI 컴포넌트로 리팩토링했습니다. 캐러셀 로직은 FeedCarousel로 이동했습니다.
  • src/pages/mycd/index.tsx
    • ActionBar 컴포넌트에서 더 이상 사용되지 않는 pageType prop을 제거했습니다.
  • src/shared/styles/mixins.ts
    • CD 회전 애니메이션을 위한 spin keyframes와 cdSpinner CSS 믹스인을 추가했습니다.
  • src/widgets/playlist/ActionBar.tsx
    • pageType prop을 제거하고, 좋아요 버튼 렌더링 로직을 간소화했습니다.
  • src/widgets/playlist/PlaylistLayout.tsx
    • CdSpinner 스타일을 shared/styles/mixins.ts의 cdSpinner 믹스인을 사용하도록 변경했습니다.
Activity
  • PR 작성자가 좋아요 해제 및 플리 삭제 과정에서 데이터 갱신과 관련된 서버 이슈가 있음을 언급했으며, 현재 수정 요청 중이라고 밝혔습니다. 수정 후에도 동일 이슈 발생 시 QA를 통해 추가 확인 예정입니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

피드 CD 재생용 서버측 캐러셀 엔드포인트와 타입을 추가하고, 클라이언트에서 캐러셀 페이지네이션 훅과 FeedCarousel/CarouselItem으로 UI를 재구성하여 라우터 컨텍스트 기반 상태 전달로 전환했습니다.

Changes

Cohort / File(s) Summary
API & Types
src/entities/playlist/api/playlist.ts, src/entities/playlist/types/playlist.ts
getCdCarousel, getLikedCdCarousel API 엔드포인트 추가 및 CarouselDirection, CarouselSort, CarouselParams, CarouselCdListResponse 타입 정의 추가.
Hooks / Model
src/entities/playlist/model/usePlaylists.ts, src/entities/playlist/model/useMyCd.ts
useCarouselCdList 훅 추가(양방향 cursor 기반 페이징), 토글 퍼블릭 성공 시 ['playlistDetail', cdId] 캐시 무효화 추가.
Feed 레이아웃 & 컨텍스트
src/pages/feed/FeedLayout.tsx, src/pages/feed/index.tsx, src/pages/feed/ui/layout/CdPlayerLayout.tsx
라우터 아웃렛 컨텍스트 사용으로 isOwner/isMyFeed 전달 방식 변경 및 로딩 처리 단순화.
페이지 단순화
src/pages/feed/cds/index.tsx, src/pages/feed/likes/index.tsx
복잡한 페이지 로직 제거 후 각각 FeedCarousel을 직접 렌더링하도록 변경.
Carousel UI 교체
src/pages/feed/ui/CdCarousel.tsx, src/pages/feed/ui/CarouselItem.tsx, src/pages/feed/ui/FeedCarousel.tsx, src/pages/feed/ui/index.ts
기존 CdCarousel 제거, CdViewerLayoutCarouselItem으로 대체, 새로운 FeedCarousel 추가(페이징·상세 로드 책임).
연관 UI 변경
src/pages/feed/ui/FeedCdList.tsx, src/pages/mycd/index.tsx, src/widgets/playlist/ActionBar.tsx, src/widgets/playlist/PlaylistLayout.tsx
라우터 상태에 currentSort 포함, ActionBarpageType prop 제거, CdSpinner 스타일을 공통 mixin으로 이동.
스타일 유틸 추가
src/shared/styles/mixins.ts
spin keyframes 및 cdSpinner mixin 추가(재사용 가능한 회전 애니메이션).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant FeedLayout as FeedLayout\n(outlet context)
    participant FeedCarousel as FeedCarousel\n(useCarouselCdList)
    participant API as Playlist API
    participant Detail as PlaylistDetail\n(usePlaylistDetail)
    participant CarouselItem as CarouselItem

    User->>FeedLayout: 피드 진입 (shareCode)
    FeedLayout->>FeedCarousel: isOwner 컨텍스트 전달
    User->>FeedCarousel: 캐러셀 초기 로드
    FeedCarousel->>API: getCdCarousel / getLikedCdCarousel (params)
    API-->>FeedCarousel: paginated carousel data
    FeedCarousel->>Detail: usePlaylistDetail(centerPlaylistId)
    Detail-->>FeedCarousel: playlistDetail
    FeedCarousel->>CarouselItem: playlistData + playlistDetail + centerItem
    User->>CarouselItem: 스와이프 / 선택
    CarouselItem->>FeedCarousel: handleCenterChange(newId)
    FeedCarousel->>API: next/prev page 요청 (필요시)
    API-->>FeedCarousel: 추가 페이지 데이터
    FeedCarousel->>Detail: 새 center에 대한 상세 재요청
    Detail-->>FeedCarousel: 업데이트된 playlistDetail
    FeedCarousel->>CarouselItem: 업데이트 전달
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

HIGH

Suggested reviewers

  • hansololiviakim

Poem

🐰 캐러셀 둥실 돌아가네,
API가 곡을 틀어주고,
페이지는 손끝으로 넘겨요,
중앙엔 내가 좋아하는 노래,
토끼가 박수 쳐요 🎶✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 '[feat] 피드 CD 재생 API 연결'은 변경 사항의 주요 목적인 피드 CD 재생 API 연결을 명확하고 간결하게 설명합니다.
Description check ✅ Passed PR 설명은 관련 이슈(#194)를 명시하고 주요 변경 사항과 PR 포인트를 제공합니다. 일부 선택 항목(테스트 방법, 알게된 점, 참고 자료)이 미작성되었으나 필수 항목은 충분히 작성되었습니다.
Linked Issues check ✅ Passed PR은 이슈 #194의 피드 CD 재생 API 연결 요구사항(나의 CD, 좋아요한 CD 지원)을 충족합니다. 새로운 API 엔드포인트 추가, useCarouselCdList 훅, 타입 정의 및 UI 컴포넌트 구현을 통해 두 기능을 모두 지원합니다.
Out of Scope Changes check ✅ Passed 스타일 유틸리티 추가(spin 애니메이션), ActionBar 컴포넌트 리팩토링, 플리 삭제 시 캐시 무효화 로직 개선 등이 포함되었으나, 이들은 모두 피드 CD 재생 API 기능 구현을 지원하는 필수적인 변경사항입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#194/feed-cd-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 피드에서 CD를 재생하는 기능을 구현하고, 관련 API를 연동하는 작업을 포함합니다. 전반적으로 isOwner 상태를 FeedLayout에서 관리하여 하위 컴포넌트로 전달하는 구조 개선과, FeedCarousel 컴포넌트를 도입하여 cdslikes 페이지의 중복 로직을 제거한 점이 인상적입니다. 이는 코드의 재사용성과 유지보수성을 크게 향상시킬 것입니다. 또한, useInfiniteQuery를 활용한 양방향 캐러셀 구현과 cdSpinner 스타일 믹스인 추출 등 좋은 리팩토링이 많이 이루어졌습니다. 다만, ActionBar 컴포넌트에서 자신의 CD에 '좋아요' 버튼이 표시될 수 있는 가능성이 있어 보이는 부분을 발견하여 리뷰 코멘트를 남겼습니다. 이 부분을 확인해주시면 좋을 것 같습니다.

@github-actions
Copy link

🎵 Storybook Link 🎵
🔗 https://689dbb45f8d09aea7832eeb1-wswyqaohqa.chromatic.com/

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/widgets/playlist/ActionBar.tsx (1)

32-37: ⚠️ Potential issue | 🟠 Major

onClick 핸들러를 SvgButton에 직접 전달하세요.

현재 onClick={handleMovePlaylist}가 wrapper div(DetailButton)에 있지만, 내부의 <SvgButton>은 네이티브 <button>을 렌더링합니다. SvgButtononClick prop을 지원하므로, 핸들러를 정확한 버튼 요소로 옮겨야 합니다.

<DetailButton $isMy={type === 'MY'}>
  <SvgButton
    icon={Playlist}
    onClick={handleMovePlaylist}
    width={type === 'MY' ? 16 : 24}
    height={type === 'MY' ? 16 : 24}
  />
</DetailButton>

그리고 DetailButton에서 onClick 제거하세요. 현재 방식은 의미론적으로 올바르지 않은 div에 click handler가 있어 스크린리더 및 키보드 접근성 문제가 발생합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets/playlist/ActionBar.tsx` around lines 32 - 37, The click handler
is attached to the wrapper DetailButton div instead of the actual button
element; move the onClick from DetailButton to the inner SvgButton by removing
onClick={handleMovePlaylist} from DetailButton and adding
onClick={handleMovePlaylist} to the SvgButton (keep width/height/ icon logic
intact) so the native button rendered by SvgButton receives the event
(references: DetailButton, SvgButton, handleMovePlaylist); this also removes the
non-semantic click on a div to restore proper keyboard/ARIA behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/entities/playlist/model/usePlaylists.ts`:
- Around line 156-163: The query key for useInfiniteQuery in useCarouselCdList
currently includes params.anchorId which causes the infinite query to reset on
every carousel center-card change; remove params.anchorId from the queryKey
(keep ['feedCdList', type, shareCode, params.sort]) and instead pass
params.anchorId only into the fetch function as an initial seed; when the anchor
truly needs to trigger a reload, call the queryClient.invalidateQueries or
queryClient.resetQueries / refetch manually from the component (or expose a
reset/refetch function from useCarouselCdList) so anchor changes do not recreate
the whole infinite query state.

In `@src/pages/feed/FeedLayout.tsx`:
- Around line 9-13: The current FeedLayout renders <Outlet context={{ isOwner:
data?.isOwner }} /> even when useOwnerStatus failed, causing undefined isOwner
in child routes; update the component to check useOwnerStatus's isError (and
!data) after isLoading and render an error/fallback (or null) instead of the
Outlet, and only pass data.isOwner into <Outlet context={{ isOwner: ... }}> when
the query succeeded; locate the useOwnerStatus call and the Outlet return in
FeedLayout (symbols: useOwnerStatus, isLoading, isError, data, Outlet) and
implement the early error branch so children never receive undefined isOwner.

In `@src/pages/feed/ui/CarouselItem.tsx`:
- Around line 243-247: The ActionBar is always receiving type="MY" which breaks
UI differences; update the ActionBar invocation in CarouselItem (the ActionBar
component near centerItem/playlistId/currentPlaylist usage) to pass a dynamic
type based on pageType (e.g., type={pageType === 'MY' ? 'MY' : 'DISCOVER'} or
simply type={pageType} if values align) so LikeButton, ShareButton, ChatButton
and layout behave correctly for LIKE/DISCOVER vs MY pages.

In `@src/pages/feed/ui/FeedCarousel.tsx`:
- Around line 23-24: The carousel can hang when routePlaylistId exists but
cannot be resolved (NaN, not present in the fetched list, or list is empty), so
update FeedCarousel to defensively handle that: validate routePlaylistId before
using it (ensure Number(routePlaylistId) is finite), and if the playlist cannot
be found set anchorId to undefined or explicitly set a fallback/empty state so
centerItem is initialized (or trigger the component's error/fallback path).
Specifically, adjust the logic around currentSort and anchorId initialization
and add a guard where centerItem is computed (the code that currently relies on
!centerItem.playlistId at runtime) to short-circuit into an empty-state or
isError path when the list is empty or the anchor lookup fails. Ensure this uses
existing error handling (onError/try-catch or state flag) rather than letting
the spinner run indefinitely.

In `@src/pages/feed/ui/layout/CdPlayerLayout.tsx`:
- Around line 31-39: CdPlayerLayout currently calls useOutletContext() without a
type so undefined/any flows into children; change the hook to
useOutletContext<ShareCodeOwnerResponse | null>() in CdPlayerLayout (and
validate consumers) and update the provider (FeedLayout) to never pass undefined
by returning null or a safe default for data?.isOwner (or explicitly provide a
ShareCodeOwnerResponse|null) so child components like FeedCarousel and
downstream routes receive a typed value and handle the null case instead of
undefined.

---

Outside diff comments:
In `@src/widgets/playlist/ActionBar.tsx`:
- Around line 32-37: The click handler is attached to the wrapper DetailButton
div instead of the actual button element; move the onClick from DetailButton to
the inner SvgButton by removing onClick={handleMovePlaylist} from DetailButton
and adding onClick={handleMovePlaylist} to the SvgButton (keep width/height/
icon logic intact) so the native button rendered by SvgButton receives the event
(references: DetailButton, SvgButton, handleMovePlaylist); this also removes the
non-semantic click on a div to restore proper keyboard/ARIA behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 370e73e0-de20-4fab-b409-1b49a554875a

📥 Commits

Reviewing files that changed from the base of the PR and between 7290a49 and 18dfa1e.

📒 Files selected for processing (18)
  • src/entities/playlist/api/playlist.ts
  • src/entities/playlist/model/useMyCd.ts
  • src/entities/playlist/model/usePlaylists.ts
  • src/entities/playlist/types/playlist.ts
  • src/pages/feed/FeedLayout.tsx
  • src/pages/feed/cds/index.tsx
  • src/pages/feed/index.tsx
  • src/pages/feed/likes/index.tsx
  • src/pages/feed/ui/CarouselItem.tsx
  • src/pages/feed/ui/CdCarousel.tsx
  • src/pages/feed/ui/FeedCarousel.tsx
  • src/pages/feed/ui/FeedCdList.tsx
  • src/pages/feed/ui/index.ts
  • src/pages/feed/ui/layout/CdPlayerLayout.tsx
  • src/pages/mycd/index.tsx
  • src/shared/styles/mixins.ts
  • src/widgets/playlist/ActionBar.tsx
  • src/widgets/playlist/PlaylistLayout.tsx
💤 Files with no reviewable changes (2)
  • src/pages/mycd/index.tsx
  • src/pages/feed/ui/CdCarousel.tsx

Comment on lines +156 to +163
export const useCarouselCdList = (
type: FEED_CD_LIST_TAB_TYPE, // cds or likes
shareCode: string,
params: CarouselParams
) => {
return useInfiniteQuery({
queryKey: ['feedCdList', type, shareCode, params.sort, params.anchorId],

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

anchorId가 cache key에 들어가서 카드 이동마다 infinite query가 리셋됩니다.

FeedCarousel은 중심 카드가 바뀔 때마다 route id를 갱신합니다. 지금 키에 params.anchorId가 포함돼 있어서 스와이프 한 번마다 새 쿼리가 생성되고, 이미 받아온 pages를 버린 채 다시 로딩하게 됩니다. 이 훅의 양방향 프리패칭 이점이 거의 사라집니다.

♻️ 수정 방향 예시
   return useInfiniteQuery({
-    queryKey: ['feedCdList', type, shareCode, params.sort, params.anchorId],
+    queryKey: ['feedCdList', type, shareCode, params.sort, params.limit],

anchorId는 초기 진입 seed로만 사용하고, 실제 재-anchor가 필요할 때만 별도로 reset/refetch 하는 편이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/playlist/model/usePlaylists.ts` around lines 156 - 163, The
query key for useInfiniteQuery in useCarouselCdList currently includes
params.anchorId which causes the infinite query to reset on every carousel
center-card change; remove params.anchorId from the queryKey (keep
['feedCdList', type, shareCode, params.sort]) and instead pass params.anchorId
only into the fetch function as an initial seed; when the anchor truly needs to
trigger a reload, call the queryClient.invalidateQueries or
queryClient.resetQueries / refetch manually from the component (or expose a
reset/refetch function from useCarouselCdList) so anchor changes do not recreate
the whole infinite query state.

Comment on lines +9 to +13
const { data, isLoading } = useOwnerStatus(shareCode || '')

if (isLoading) return <Loading isLoading />

return <Outlet context={{ isOwner: data?.isOwner }} />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

오너 상태 조회 실패를 정상 렌더로 넘기지 마세요.

여기서는 useOwnerStatus가 실패해도 그대로 Outlet을 렌더링해서 isOwner: undefined가 하위 라우트로 내려갑니다. 그러면 실제 소유자여도 편집/삭제 UI가 숨겨지고 헤더 문구도 잘못 나옵니다. 로딩이 끝난 뒤에는 isError || !data를 먼저 처리하고, 성공한 경우에만 data를 context로 넘기는 쪽이 안전합니다.

🐛 수정 예시
-import { Outlet, useParams } from 'react-router-dom'
+import { Navigate, Outlet, useParams } from 'react-router-dom'
...
-  const { data, isLoading } = useOwnerStatus(shareCode || '')
+  const { data, isLoading, isError } = useOwnerStatus(shareCode)
...
-  return <Outlet context={{ isOwner: data?.isOwner }} />
+  if (isError || !data) return <Navigate to="/error" replace />
+
+  return <Outlet context={data} />
As per coding guidelines: 상태 관리 - 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data, isLoading } = useOwnerStatus(shareCode || '')
if (isLoading) return <Loading isLoading />
return <Outlet context={{ isOwner: data?.isOwner }} />
import { Navigate, Outlet, useParams } from 'react-router-dom'
const { data, isLoading, isError } = useOwnerStatus(shareCode)
if (isLoading) return <Loading isLoading />
if (isError || !data) return <Navigate to="/error" replace />
return <Outlet context={data} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/FeedLayout.tsx` around lines 9 - 13, The current FeedLayout
renders <Outlet context={{ isOwner: data?.isOwner }} /> even when useOwnerStatus
failed, causing undefined isOwner in child routes; update the component to check
useOwnerStatus's isError (and !data) after isLoading and render an
error/fallback (or null) instead of the Outlet, and only pass data.isOwner into
<Outlet context={{ isOwner: ... }}> when the query succeeded; locate the
useOwnerStatus call and the Outlet return in FeedLayout (symbols:
useOwnerStatus, isLoading, isError, data, Outlet) and implement the early error
branch so children never receive undefined isOwner.

Comment on lines 243 to 247
<ActionBar
playlistId={centerItem.playlistId ?? 0}
creatorId={currentPlaylist.creatorId}
stickers={playlistDetail?.cdResponse?.cdItems || []}
stickers={playlistDetail.cdResponse.cdItems}
type="MY"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== ActionBar 관련 선언 ==="
fd 'ActionBar' src -t f -x sed -n '1,240p' {}

echo
echo "=== ActionBar 사용처 ==="
rg -nP --type=ts --type=tsx '<ActionBar\b' src -C 3

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 1766


🏁 Script executed:

# CarouselItem.tsx 전체 구조와 pageType 정의 확인
cat -n src/pages/feed/ui/CarouselItem.tsx | head -280 | tail -100

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 4128


🏁 Script executed:

# ActionBar를 사용하는 모든 위치 (grep 사용, rg 대신)
grep -rn "ActionBar" src --include="*.ts" --include="*.tsx" -A 5 | head -100

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 5532


🏁 Script executed:

# CarouselItem.tsx에서 pageType이 어떻게 정의되고 사용되는지 확인
grep -n "pageType" src/pages/feed/ui/CarouselItem.tsx

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 400


🏁 Script executed:

# LikeButton, ChatButton, ShareButton의 type 핸들링 확인
find src -name "LikeButton.tsx" -o -name "ChatButton.tsx" -o -name "ShareButton.tsx" | head -10

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 185


🏁 Script executed:

# 각 컴포넌트의 type prop 사용 방식
grep -rn "type\s*=" src/features/like src/features/share src/widgets/chat --include="*.tsx" -A 2 | grep -E "(LikeButton|ChatButton|ShareButton|interface|type\s*[?:])" | head -50

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 2794


pageType'LIKE'일 때도 ActionBar'MY'를 고정으로 전달하고 있습니다.

자식 컴포넌트들이 type prop을 받아 다르게 처리하므로:

  • LikeButtontype='MY'일 때 "좋아요" 라벨을 표시 (좋아요 탭에서 불필요)
  • ShareButtonChatButtontype='MY'일 때 아이콘이 16×16으로 축소 (DISCOVER 시 24×24)
  • ActionBar 레이아웃도 type='MY'일 때 flexRowCenter, type='DISCOVER'일 때 flexColCenter로 다름

pageType에 따라 ActionBar의 타입을 동적으로 전달해야 합니다 (예: type={pageType === 'MY' ? 'MY' : 'DISCOVER'}).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/ui/CarouselItem.tsx` around lines 243 - 247, The ActionBar is
always receiving type="MY" which breaks UI differences; update the ActionBar
invocation in CarouselItem (the ActionBar component near
centerItem/playlistId/currentPlaylist usage) to pass a dynamic type based on
pageType (e.g., type={pageType === 'MY' ? 'MY' : 'DISCOVER'} or simply
type={pageType} if values align) so LikeButton, ShareButton, ChatButton and
layout behave correctly for LIKE/DISCOVER vs MY pages.

Comment on lines +23 to +24
const [currentSort] = useState(() => state?.currentSort ?? 'RECENT')
const anchorId = routePlaylistId ? Number(routePlaylistId) : undefined
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

anchor를 못 찾는 케이스를 처리하지 않으면 여기서 spinner가 끝나지 않습니다.

routePlaylistId가 있지만 응답에 해당 CD가 없거나, Number(routePlaylistId)NaN이거나, 목록이 빈 배열이면 centerItem이 끝까지 설정되지 않습니다. 그러면 Line 109의 !centerItem.playlistId가 계속 참이어서 isError/fallback 분기로 절대 내려가지 못합니다. 삭제·언라이크 직후나 잘못된 deep link에서 화면이 무한 로딩 상태로 남습니다.

🐛 최소 수정 예시
-  if (isLoading || isDetailLoading || !centerItem.playlistId) {
+  if (isLoading || isDetailLoading) {
     return <Loading isLoading />
   }

-  if (!data || !playlistDetail || isError) {
+  if (isError || !playlistData.length || !centerItem.playlistId || !playlistDetail) {
     return <Navigate to="/error" replace />
   }

빈 목록을 에러로 보내고 싶지 않다면, 같은 위치에 별도 empty state를 두세요.

As per coding guidelines: 상태 관리 - 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용.

Also applies to: 51-75, 109-115

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/ui/FeedCarousel.tsx` around lines 23 - 24, The carousel can
hang when routePlaylistId exists but cannot be resolved (NaN, not present in the
fetched list, or list is empty), so update FeedCarousel to defensively handle
that: validate routePlaylistId before using it (ensure Number(routePlaylistId)
is finite), and if the playlist cannot be found set anchorId to undefined or
explicitly set a fallback/empty state so centerItem is initialized (or trigger
the component's error/fallback path). Specifically, adjust the logic around
currentSort and anchorId initialization and add a guard where centerItem is
computed (the code that currently relies on !centerItem.playlistId at runtime)
to short-circuit into an empty-state or isError path when the list is empty or
the anchor lookup fails. Ensure this uses existing error handling
(onError/try-catch or state flag) rather than letting the spinner run
indefinitely.

Comment on lines +31 to +39
const isOwner = useOutletContext()

const videoId = currentPlaylist
? getVideoId(currentPlaylist.songs[currentTrackIndex]?.youtubeUrl)
: null

return (
<>
<Outlet />
<Outlet context={isOwner} />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
for file in \
  src/pages/feed/FeedLayout.tsx \
  src/pages/feed/ui/layout/CdPlayerLayout.tsx \
  src/pages/feed/index.tsx
do
  echo "### $file"
  rg -n -C2 'Outlet context|useOutletContext|isOwner' "$file"
done

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 1079


🏁 Script executed:

# ShareCodeOwnerResponse 타입 정의 확인
rg -n 'type ShareCodeOwnerResponse|interface ShareCodeOwnerResponse' -A3

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 588


🏁 Script executed:

# FeedLayout.tsx 전체 내용으로 data 상태와 로딩 처리 확인
cat -n src/pages/feed/FeedLayout.tsx

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 598


🏁 Script executed:

# CdPlayerLayout.tsx의 하위 라우트 구조 확인
rg -n '<Outlet|useOutletContext' -B3 -A3 src/pages/feed/

Repository: dnd-side-project/dnd-13th-8-frontend

Length of output: 4028


CdPlayerLayout에서 useOutletContext에 제네릭 타입을 지정하고, provider의 undefined 상태를 처리하세요.

useOutletContext()는 타입이 없어서 any와 동일하며, 상위 FeedLayout에서 data?.isOwner(undefined 가능)를 그대로 넘기고 있습니다. 현재 코드는 하위 라우트(src/pages/feed/index.tsx, src/pages/feed/ui/FeedCarousel.tsx)가 기대하는 ShareCodeOwnerResponse 타입을 검증하지 못하므로, owner-status 조회 실패 시 undefined가 모든 하위 라우트로 퍼져 타입 에러가 발생할 수 있습니다.

수정 방안:

  • const isOwner = useOutletContext<ShareCodeOwnerResponse>() 로 명시적 타입 지정
  • 또는 FeedLayout에서 data가 없으면 null을 반환하거나 기본값을 제공하여 중간 계층부터 undefined 상태 차단

스타일 가이드 "타입스크립트: strict 모드 사용, 타입 명시적으로 작성 (any 사용 지양)" 위반입니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/ui/layout/CdPlayerLayout.tsx` around lines 31 - 39,
CdPlayerLayout currently calls useOutletContext() without a type so
undefined/any flows into children; change the hook to
useOutletContext<ShareCodeOwnerResponse | null>() in CdPlayerLayout (and
validate consumers) and update the provider (FeedLayout) to never pass undefined
by returning null or a safe default for data?.isOwner (or explicitly provide a
ShareCodeOwnerResponse|null) so child components like FeedCarousel and
downstream routes receive a typed value and handle the null case instead of
undefined.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/pages/feed/index.tsx (1)

29-29: outlet context가 없을 경우 방어 코드 고려

useOutletContext는 outlet context가 없을 경우 undefined를 반환할 수 있습니다. FeedLayout에서 항상 context를 제공한다고 가정하지만, 라우트 구조 변경 시 런타임 에러가 발생할 수 있습니다.

🛡️ 방어 코드 제안
- const { isOwner: isMyFeed } = useOutletContext<ShareCodeOwnerResponse>()
+ const outletContext = useOutletContext<ShareCodeOwnerResponse>()
+ const isMyFeed = outletContext?.isOwner ?? false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/index.tsx` at line 29,
useOutletContext<ShareCodeOwnerResponse>() can return undefined if no outlet
context is provided; add a defensive guard around the call in the feed page to
avoid runtime errors (e.g., check the returned value before destructuring or
provide a safe default). Locate the usage of useOutletContext and the isMyFeed
variable in src/pages/feed/index.tsx and update it so that it safely handles
undefined (for example, call useOutletContext into a variable like outletContext
and then derive isMyFeed = outletContext?.isOwner ?? false), and ensure this
remains consistent with FeedLayout which normally provides the context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/pages/feed/index.tsx`:
- Line 29: useOutletContext<ShareCodeOwnerResponse>() can return undefined if no
outlet context is provided; add a defensive guard around the call in the feed
page to avoid runtime errors (e.g., check the returned value before
destructuring or provide a safe default). Locate the usage of useOutletContext
and the isMyFeed variable in src/pages/feed/index.tsx and update it so that it
safely handles undefined (for example, call useOutletContext into a variable
like outletContext and then derive isMyFeed = outletContext?.isOwner ?? false),
and ensure this remains consistent with FeedLayout which normally provides the
context.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2cd72372-f02e-4716-8bd1-bd4e0226a9da

📥 Commits

Reviewing files that changed from the base of the PR and between 18dfa1e and 4a8aa9b.

📒 Files selected for processing (4)
  • src/entities/playlist/types/playlist.ts
  • src/pages/feed/index.tsx
  • src/pages/feed/ui/FeedCdList.tsx
  • src/pages/feed/ui/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/feed/ui/FeedCdList.tsx
  • src/entities/playlist/types/playlist.ts

Copy link
Member

@hansololiviakim hansololiviakim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isOwner 판단 로직을 FeedLayout에서 담당하도록 변경
feed 하위 여러 라우트에서 동일하게 사용하는 isOwner 판단 로직을 중복 없이 관리하기 위해 공통 부모인 FeedLayout에서 계산하고 Outlet context로 전달하도록 구조를 변경했습니다. 일부 라우트에서 사용하지 않더라도 공통 로직을 상위 레벨에 두는 것이 유지보수성과 상태 일관성 측면에서 더 적합하다고 판단했습니다.

너무 좋습니다~! 이제 2차 스프린트도 끝이 보이네요 ㅎ.ㅎ 고생하셨습니다!

@maylh maylh merged commit 1a9415e into develop Mar 12, 2026
4 checks passed
@maylh maylh deleted the feat/#194/feed-cd-api branch March 12, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 피드 CD 재생 api 연결

2 participants