Skip to content

[feat] 마이페이지 UI/API 변경사항 반영#115

Merged
hansololiviakim merged 30 commits intodevelopfrom
feature/#105/playlist-add
Oct 24, 2025
Merged

[feat] 마이페이지 UI/API 변경사항 반영#115
hansololiviakim merged 30 commits intodevelopfrom
feature/#105/playlist-add

Conversation

@hansololiviakim
Copy link
Member

@hansololiviakim hansololiviakim commented Oct 23, 2025

🛰️ 관련 이슈


✨ 주요 변경 사항

▪️ 기능

  • 대표 플레이리스트 UI 및 기능 제거
  • src/pages/myPage → src/pages/mypage 폴더명, 파일명 수정
  • 네이밍 통일에 따른 /playlist → /tracklist URL 수정, useMyPlaylist → useMyCd 파일명 수정
  • CD 생성/수정 step1~3 UI 수정사항 반영 및 리팩토링
  • CD url 공유 로직 hook으로 분리

▪️ UI

  • 나의 팔로잉 → 나의 좋아요 수정사항 반영
  • 알림 기능 2차 스프린트로 변경되어 전체 페이지 내 알림 UI 주석 처리
  • 나의 CD 리스트 출력 시 반응형 코드 수정
  • 비공개 CD UI 추가

🗯️ PR 포인트

  • 파일명이랑 폴더명을 많이 바꿔서 병합 신경써서 해보겠습니다🥲
  • 마이페이지 내 좋아요 기능은 병합 후 작업 예정입니다!
  • 병합 후에 미사용 타입이나 타입 흩어져있는 것들 정리해보고 공유드리겠습니다!

🚀 알게된 점

  • 없음

📖 참고 자료 (선택)

  • 없음

Summary by CodeRabbit

릴리스 노트

  • New Features

    • CD 생성·임시저장·최종저장 편집 흐름 추가 및 단계별 UI 개선
    • 좋아요한 CD·내 CD 목록 UI, 정렬 및 상세(트랙리스트) 뷰 개선
    • CD 공개/비공개 토글, CD 삭제, 공유 URL 복사 기능 추가
    • 트랙 드래그앤드롭 재정렬, 썸네일 검증 및 편집 지원
    • 회원 탈퇴(계정 삭제) 화면 추가
  • Bug Fixes

    • 세션 만료·저장·업로드 오류 처리 및 사용자 알림 개선
  • Refactor

    • 마이페이지·커스터마이즈 관련 페이지 구조·네이밍 정리
  • Style

    • 전역 링크·입력 스타일 정리 및 접근성/모달 설명문구 추가

- 백엔드 response 형식 변경에 맞춰 key값 수정
- 의미, 형식에 맞게 변수/함수/로직 개편
@hansololiviakim hansololiviakim self-assigned this Oct 23, 2025
@hansololiviakim hansololiviakim added the HIGH 빠르게 처리해야 하는 높은 우선순위 label Oct 23, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 23, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

마이페이지/플레이리스트 중심을 CD 모델로 전환: API·타입 재정의, 훅 마이그레이션, 커스터마이즈(CD) 흐름 추가, 공유 복사 훅 도입, 다수 UI 컴포넌트 생성/제거 및 네임스페이스(myPage → mypage) 정리. 접근성·글로벌 스타일·토스트 항목도 일부 추가/수정됨.

Changes

Cohort / File(s) 변경 요약
루트·접근성
src/App.tsx
비모바일 장식 요소 Fragment → div aria-hidden="true"로 변경.
토스트·계정 삭제 훅
src/app/providers/ToastProvider.tsx, src/features/auth/api/auth.ts, src/features/auth/model/useAuth.ts
ToastType에 'CD_DELETE' 추가 및 메시지, deleteAccount API와 useDeleteAccount 훅 추가.
아이콘·글로벌 스타일·스토리
src/assets/icons/index.ts, src/shared/styles/globalStyle.ts, index.html, src/stories/*
아이콘 바렐 exports 변경(추가/제거/이름 변경), 글로벌 a/textarea 스타일 및 format-detection 메타 추가, 스토리 import/데이터( orderIndex ) 보정.
플레이리스트 → CD API·타입
src/entities/playlist/api/playlist.ts, src/entities/playlist/types/playlist.ts
엔드포인트·함수명·타입 재정의(예: CdMetaResponse, cdResponse, isPublic 등), 기존 일부 함수 제거 및 신규 CD 엔드포인트 추가.
쿼리 훅 재배치
src/entities/playlist/model/useMyCd.ts, src/entities/playlist/model/useMyPlaylist.ts (삭제), src/entities/playlist/model/usePlaylists.ts
useMyPlaylist 모듈 삭제, useMyCd(목록/좋아요/액션) 추가, usePlaylistDetail에 options.enabled 인자 추가.
커스터마이즈 흐름 (API/훅/타입)
src/features/customize/api/customize.ts, src/features/customize/model/useCustomize.ts, src/features/customize/types/customize.ts
플레이리스트 임시/최종 흐름 제거 → CD 기반 API( postCdTempSave, postCdFinalCreate/Update, getFinalCdCustom )와 훅·타입(CdTempSavePayload 등) 도입.
공유 복사 로직 이동
src/features/share/api/share.ts (삭제), src/features/share/model/useShare.ts (삭제), src/features/share/ui/ShareButton.tsx, src/shared/lib/useCopyCdShareUrl.ts
postShare/useShare 제거, 공유 복사 로직을 useCopyCdShareUrl로 이동하고 ShareButton에서 사용.
공용 UI 변경
src/shared/ui/Cd.tsx, src/shared/ui/Input.tsx, src/shared/ui/Modal.tsx
Cd에 isPublic?: booleanresponsive variant/ResizeObserver 추가, Input에 required/disabled props 추가, Modal에 description? prop 추가.
마이페이지 리팩터링 (네임스페이스·라우트)
src/pages/myPage/* (삭제 다수), src/pages/mypage/ui/** (신규/수정 다수), src/shared/config/routesConfig.ts
디렉토리·컴포넌트명 소문자화(myPage → mypage), 기존 MyPage 컴포넌트들 삭제, Mypage·MypageTracklist·CustomizeStep1/2/3·MyCdList·MyLikedCdList 등 신규 추가 및 라우트/경로명 변경.
컴포넌트·바렐·타입 미세조정
src/pages/mypage/ui/components/*, src/pages/mypage/types/mypage.ts, src/pages/mypage/ui/customize/**
CUSTOMIZE_STEP·MYPAGE_TAB_TYPE 도입, StepHeader/ToggleSwitch prop 시그니처 변경, prevPlaylistData → prevTracklist 등 명칭·타입 정리, 바렐 파일 추가/제거.
삭제된 기존 UI
src/pages/myPage/ui/customize/step1/*, src/pages/myPage/ui/components/*, src/pages/myPage/ui/*
기존 step1 컴포넌트, CdGrid, MyPagePlaylist 등 여러 파일 완전 삭제.
신규/변경된 마이페이지 컴포넌트
src/pages/mypage/ui/customize/step1/index.tsx, .../TrackItem.tsx, .../step2/index.tsx, .../step3/index.tsx, src/pages/mypage/ui/main/*, src/pages/mypage/ui/tracklist/index.tsx, src/pages/mypage/ui/unregister/index.tsx
Customize step1~3 재구성(트랙 추가/검증/임시저장/최종저장), draggable TrackItem 추가, MyCdList/MyLikedCdList/Mypage/MypageTracklist/Unregister 등 신규 페이지·컴포넌트 추가.
위젯/페이지 소소 변경
src/pages/discover/index.tsx, src/pages/mycd/index.tsx, src/pages/home/*, src/widgets/playlist/PlaylistInfo.tsx
PlaylistInfo·discover/mycd 등에서 onlyCdResponse → cdResponse, representative → isPublic 적용 및 훅 import 경로 보정.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant UI as CustomizeStep1 UI
    participant Hook as useCdTempSave
    participant API as postCdTempSave
    participant Toast

    User->>UI: YouTube URL 입력 및 트랙 추가
    UI->>UI: 비디오 ID 추출·썸네일 검증
    User->>UI: 다음 클릭
    UI->>UI: 유효성 검사
    alt 실패
        UI-->>User: 모달 오류 표시
    else 성공
        UI->>Hook: temp save 호출 (payload)
        Hook->>API: postCdTempSave(payload)
        API-->>Hook: 성공 응답
        Hook->>Toast: 'LINK' 토스트
        Hook-->>UI: 성공 → Step2로 이동
    end
Loading
sequenceDiagram
    autonumber
    participant User
    participant Tracklist as MypageTracklist
    participant Hook as useMyCdActions
    participant API as getTracklist/deleteMyCd/patchMyCdPublic
    participant Toast

    User->>Tracklist: 페이지 진입
    Tracklist->>Hook: getTracklist(cdId)
    Hook->>API: getTracklist
    API-->>Hook: PlaylistDetailResponse
    Hook-->>Tracklist: 트랙리스트 반환
    User->>Tracklist: 삭제 요청
    Tracklist->>Hook: deleteMutation(cdId)
    Hook->>API: deleteMyCd
    API-->>Hook: 성공
    Hook->>Toast: 'CD_DELETE'
    Hook-->>Tracklist: 성공 -> /mypage로 이동
    User->>Tracklist: 공개 토글
    Tracklist->>Hook: togglePublicMutation
    Hook->>API: patchMyCdPublic
    API-->>Hook: 성공 -> 트랙리스트 쿼리 무효화
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

근거: API/타입 재정의, 훅·컴포넌트 대규모 마이그레이션, UI 흐름 재설계 및 네임스페이스 변경 등 이질적 변경이 광범위하게 포함됨.

Possibly related PRs

Suggested reviewers

  • maylh

Poem

🐰 새 CD 들고 폴짝 뛰네,
타입 고치고 훅을 옮겼네,
트랙은 줄 세우고 토스트는 춤추네,
마이페이지 길 닦아 새 길로 홉—홉! 🎶

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목 "[feat] 마이페이지 UI/API 변경사항 반영"은 변경 내용의 핵심을 명확하게 요약하고 있습니다. 제목은 대표 플레이리스트 기능 제거, 폴더/파일명 변경, CD 생성/수정 단계 리팩토링, API 응답 구조 변경 등 여러 핵심 변경사항을 아우르는 주요 목표를 정확히 반영하고 있으며, 변경사항을 스캔하는 팀원들이 충분히 이해할 수 있는 수준의 구체성을 제공합니다.
Linked Issues Check ✅ Passed 연결된 이슈 #105의 코딩 관련 요구사항들이 대부분 구현되어 있습니다. 마이페이지 트랙리스트 UI 변경, CD 생성 네이밍/UI 변경, API 응답 구조 변경이 모두 반영되었으며, 좋아요 기능도 백엔드 API(getLikedCdList, useMyLikedCdList), UI 컴포넌트(MyLikedCdList), 탭 UI가 구현되었습니다. PR 설명에서 좋아요 기능이 "병합 후 작업 예정"이라고 언급했으나, 실제 코드 변경사항에는 필요한 기본 기능들이 포함되어 있어 주요 요구사항을 충족합니다.
Out of Scope Changes Check ✅ Passed 변경사항들이 이슈 #105의 목표와 PR 설명에 제시된 요구사항과 잘 맞아있습니다. 대부분의 변경은 마이페이지 UI/API 개선에 직접 관련되어 있으며, Input/Modal 컴포넌트 개선, Cd 컴포넌트의 반응형/비공개 기능 추가, 인증/삭제 기능 추가, 글로벌 스타일 개선 등은 마이페이지 리팩토링을 지원하는 필수 보조 변경사항입니다. 아이콘 추가/제거, 라우팅 변경, 타입 구조 개선 등도 모두 선언된 목표와 일치합니다.
Description Check ✅ Passed PR 설명은 저장소의 템플릿 요구사항을 대부분 충족합니다. 관련 이슈(#105), 주요 변경 사항(기능, UI 수정), PR 포인트, 알게된 점, 참고 자료 등 필수 섹션이 포함되어 있습니다. 테스트 방법/체크리스트는 명시적으로 제공되지 않았으나, 이는 저자의 의도로 보이며 전체적으로 변경사항에 대한 충분한 컨텍스트를 제공합니다. 추가로 병합 시 주의사항과 향후 작업 계획이 명확하게 커뮤니케이션되어 있습니다.

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98c7894 and 10baed0.

📒 Files selected for processing (21)
  • src/App.tsx (2 hunks)
  • src/app/providers/ToastProvider.tsx (1 hunks)
  • src/assets/icons/index.ts (2 hunks)
  • src/entities/playlist/api/playlist.ts (1 hunks)
  • src/entities/playlist/model/useMyCd.ts (1 hunks)
  • src/entities/playlist/model/usePlaylists.ts (1 hunks)
  • src/entities/playlist/types/playlist.ts (2 hunks)
  • src/features/share/ui/ShareButton.tsx (3 hunks)
  • src/pages/discover/index.tsx (1 hunks)
  • src/pages/home/index.tsx (3 hunks)
  • src/pages/home/ui/FirstSection.tsx (1 hunks)
  • src/pages/mycd/index.tsx (2 hunks)
  • src/pages/mypage/ui/customize/index.tsx (5 hunks)
  • src/pages/mypage/ui/customize/step3/index.tsx (2 hunks)
  • src/pages/mypage/ui/setting/index.tsx (4 hunks)
  • src/pages/mypage/ui/tracklist/index.tsx (1 hunks)
  • src/shared/config/routesConfig.ts (2 hunks)
  • src/shared/ui/Cd.tsx (8 hunks)
  • src/stories/Link.stories.ts (1 hunks)
  • src/stories/PlaylistWithSong.stories.ts (1 hunks)
  • src/widgets/playlist/PlaylistInfo.tsx (1 hunks)

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.

@gemini-code-assist
Copy link

Summary of Changes

Hello @hansololiviakim, 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은 마이페이지의 사용자 경험을 향상시키기 위해 UI와 API를 대대적으로 업데이트합니다. '플레이리스트' 개념을 'CD'와 '트랙리스트'로 명확히 하고, CD 생성 및 수정 과정을 더욱 직관적이고 기능적으로 개선하는 데 중점을 두었습니다. 또한, '나의 팔로잉' 기능을 '나의 좋아요'로 변경하여 사용자에게 더 관련성 높은 정보를 제공하며, 알림 기능은 향후 스프린트에서 구현될 예정입니다.

Highlights

  • 마이페이지 UI/API 개편: 마이페이지의 UI와 API가 전반적으로 개편되었습니다. '나의 팔로잉' 기능이 '나의 좋아요'로 변경되었고, '대표 플레이리스트' 기능이 제거되었습니다. 또한, '플레이리스트'라는 용어가 'CD' 또는 '트랙리스트'로 통일되어 사용됩니다.
  • CD 생성/수정 플로우 개선: CD 생성 및 수정 과정(Step 1~3)의 UI가 업데이트되고 관련 로직이 리팩토링되었습니다. 특히 Step 1에서는 유튜브 링크 유효성 검사 및 썸네일 미리보기 기능이 추가되었고, CD 공개 여부를 설정할 수 있는 토글 스위치가 도입되었습니다.
  • 파일 및 폴더 구조 변경: 네이밍 일관성을 위해 src/pages/myPage 폴더가 src/pages/mypage로 변경되었으며, 관련 파일명과 URL 경로(playlist -> tracklist)도 수정되었습니다. useMyPlaylist 훅은 useMyCd로 대체되었습니다.
  • 공유 및 삭제 기능 개선: CD URL 공유 로직이 별도의 커스텀 훅(useCopyCdShareUrl)으로 분리되어 재사용성이 높아졌습니다. 또한, CD 삭제 및 공개 여부 토글 기능이 추가되었습니다.
  • 새로운 아이콘 및 UI 요소 추가: 취소 원형(icn_cancel_circle), 눈(icn_eye), 눈 가림(icn_eye_slash), 링크(icn_link_primary), 검은색 플러스(icn_plus_black), 검은색 시작(icn_start_black) 등 새로운 아이콘들이 추가되었으며, 비공개 CD를 나타내는 UI가 도입되었습니다.
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.

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

마이페이지 관련 UI 및 API 변경사항을 반영하는 대규모 리팩토링 PR이네요. 전반적으로 파일 구조 변경, API 연동 수정, 컴포넌트 및 훅 분리 등 코드의 구조와 재사용성을 크게 개선한 점이 인상적입니다. 특히, 다음과 같은 점들이 좋았습니다:

  • myPagemypage로 변경하고 관련 컴포넌트들을 재구성하여 FSD 아키텍처에 더 부합하도록 구조를 개선했습니다.
  • useCopyCdShareUrl 훅을 새로 만들어 클립보드 복사 로직을 분리하여 재사용성을 높였습니다.
  • CD 생성/수정 플로우(step 1~3)를 리팩토링하면서 유효성 검사와 에러 핸들링을 강화하여 안정성을 높였습니다.
  • Cd 컴포넌트에 responsive variant를 추가하고 ResizeObserver를 활용하여 동적으로 크기를 조절하도록 개선한 점도 훌륭합니다.

몇 가지 추가 개선을 위해 아래에 리뷰 코멘트를 남겼습니다. 주로 네이밍 컨벤션과 타입 정의 일관성에 대한 내용으로, 코드의 명확성과 유지보수성을 더욱 향상시키는 데 도움이 될 것입니다. 전체적으로 훌륭한 작업이며, 꼼꼼하게 변경사항을 반영해주셔서 감사합니다.

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: 9

Caution

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

⚠️ Outside diff range comments (3)
src/features/customize/api/customize.ts (1)

49-53: multipart/form-data 헤더 수동 지정 제거

FormData 전송 시 Content-Type을 직접 지정하면 boundary가 누락되어 서버에서 파싱 실패할 수 있습니다. 브라우저/axios가 자동으로 설정하게 두는 게 안전합니다.

-  return api.post<UserEachStickerResponse>('/main/prop/upload', formData, {
-    headers: {
-      'Content-Type': 'multipart/form-data',
-    },
-  })
+  return api.post<UserEachStickerResponse>('/main/prop/upload', formData)
src/pages/mypage/ui/customize/step2/index.tsx (2)

963-969: React key로 uuidv4() 사용 지양 — 매 렌더마다 재마운트됨

고정 가능한 식별자(id)로 key를 지정하세요.

- {stickerList.map((id) => (
-   <li key={uuidv4()}>
+ {stickerList.map((id) => (
+   <li key={id}>
      <StickerButton type="button" onClick={() => onStickersAddClick(id)}>
        <img src={stickerUrls[id - 1]} alt={`${currentThemeId} 스티커`} />
      </StickerButton>
    </li>
 ))}

389-399: z 계층(s.z) 값이 재정렬/삭제 시 갱신되지 않음 — 저장 시 레이어 불일치 위험

클릭으로 최상단 이동하거나 삭제 후 stickers 배열 순서는 바뀌지만 s.z는 그대로라 서버 저장(zCoordinate)이 실제 레이어와 달라질 수 있습니다. 재정렬/삭제 시 z를 재계산하세요.

-          const newStickers = [...stickers]
+          const newStickers = [...stickers]
           const clickedSticker = newStickers.splice(i, 1)[0]
-          newStickers.push(clickedSticker)
-          setStickers(newStickers)
+          newStickers.push(clickedSticker)
+          setStickers(newStickers.map((s, idx) => ({ ...s, z: idx + 1 })))
           setSelectedSticker(newStickers.length - 1)
-  const deleteSelectedSticker = () => {
+  const deleteSelectedSticker = () => {
     if (selectedSticker !== null) {
-      const updatedStickers = stickers.filter((_, index) => index !== selectedSticker)
-      setStickers(updatedStickers)
+      const updatedStickers = stickers
+        .filter((_, index) => index !== selectedSticker)
+        .map((s, idx) => ({ ...s, z: idx + 1 }))
+      setStickers(updatedStickers)
       setSelectedSticker(null)
     }
   }

Also applies to: 300-305

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

24-25: TODO 주석 포맷을 코딩 컨벤션에 맞게 수정하세요.

팀 코딩 가이드라인에 따르면 TODO 주석 형식은 // TODO: 설명 - 작성자이어야 합니다. 현재 작성자 정보가 누락되어 있습니다.

다음과 같이 수정하는 것을 권장합니다:

-  // TODO: 알림 기능 2차 스프린트 시 작업 예정
+  // TODO: 알림 기능 2차 스프린트 시 작업 예정 - hansololiviakim
   // const handleNotiClick = () => navigate('/mypage/notification')
-            {/* TODO: 알림 기능 2차 스프린트 시 작업 예정 */}
+            {/* TODO: 알림 기능 2차 스프린트 시 작업 예정 - hansololiviakim */}
             {/* <SvgButton icon={Notification} onClick={handleNotiClick} /> */}

As per coding guidelines

Also applies to: 50-51

src/pages/mypage/types/mypage.ts (1)

1-3: 타입 네이밍 컨벤션을 PascalCase로 변경하는 것을 권장합니다.

TypeScript 컨벤션에 따르면 타입명은 PascalCase를 사용하는 것이 표준입니다. SCREAMING_SNAKE_CASE는 일반적으로 상수 값에 사용됩니다.

다음과 같이 수정하는 것을 권장합니다:

-export type CUSTOMIZE_STEP = 1 | 2 | 3
+export type CustomizeStep = 1 | 2 | 3

-export type MYPAGE_TAB_TYPE = 'cd' | 'like'
+export type MypageTabType = 'cd' | 'like'

코딩 가이드라인에 명시된 네이밍 컨벤션(PascalCase for types)을 준수합니다.

src/pages/mypage/ui/main/index.tsx (1)

19-22: TAB_LIST를 컴포넌트 외부로 이동하는 것을 권장합니다.

현재 TAB_LIST가 컴포넌트 내부에 정의되어 있어 매 렌더링마다 새로 생성됩니다.

다음과 같이 컴포넌트 외부로 이동하면 불필요한 재생성을 방지할 수 있습니다:

+const TAB_LIST: { label: string; value: MYPAGE_TAB_TYPE }[] = [
+  { label: '나의 CD', value: 'cd' },
+  { label: '나의 좋아요', value: 'like' },
+]
+
 const Mypage = () => {
   const navigate = useNavigate()
 
   const { selected: currentTab, onSelect: setCurrentTab } = useSingleSelect<MYPAGE_TAB_TYPE>('cd')
 
-  const TAB_LIST: { label: string; value: MYPAGE_TAB_TYPE }[] = [
-    { label: '나의 CD', value: 'cd' },
-    { label: '나의 좋아요', value: 'like' },
-  ]
src/shared/lib/useCopyCdShareUrl.ts (1)

23-27: 에러 처리 개선을 권장합니다.

현재 console.error만 사용하고 있는데, 사용자에게 복사 실패를 알리는 것이 좋을 수 있습니다.

다음과 같이 에러 토스트를 추가하는 것을 고려해보세요:

         try {
           document.execCommand('copy')
         } catch (e) {
           console.error(e)
+          toast('ERROR') // 또는 적절한 에러 메시지
+          return
         }
src/pages/mypage/ui/main/components/MyCdList.tsx (1)

61-76: MyLikedCdList와 스타일 코드가 중복됩니다.

CD_LIST_GAP, CdListWrap, CdButton이 MyLikedCdList.tsx와 거의 동일하게 중복되어 있습니다. 공통 스타일을 별도 파일로 추출하거나 믹스인으로 관리하는 것을 권장합니다.

예를 들어:

  • src/pages/mypage/ui/main/components/styles.ts 생성
  • 공통 상수와 스타일 컴포넌트를 export하여 재사용

Also applies to: 96-101

src/pages/mypage/ui/tracklist/index.tsx (1)

162-166: 매 렌더링마다 songs를 정렬하고 있습니다.

현재 구조에서는 컴포넌트가 리렌더링될 때마다 sort()가 실행됩니다. 정렬된 목록을 useMemo로 메모이제이션하면 불필요한 계산을 방지할 수 있습니다.

다음과 같이 개선할 수 있습니다:

+const sortedSongs = useMemo(
+  () => cdMetadata?.songs?.slice().sort((a, b) => a.orderIndex - b.orderIndex) ?? [],
+  [cdMetadata?.songs]
+)

  <TracklistContainer>
-   {cdMetadata?.songs
-     .sort((a, b) => a.orderIndex - b.orderIndex)
-     .map((track) => (
+   {sortedSongs.map((track) => (
      <Link key={track.id} data={track} />
    ))}
  </TracklistContainer>

참고: slice()를 사용하여 원본 배열을 변경하지 않도록 합니다.

src/entities/playlist/types/playlist.ts (1)

28-28: CdMetaResponse 타입 정의를 확인하세요.

CdMetaResponse가 배열 타입((CdBasicInfo & OnlyCdResponse)[])으로 정의되어 있습니다. 이는 API 응답이 항상 배열임을 의미하는데, 타입 이름에 복수형이나 List/Array 같은 접미사가 없어 단일 객체로 오해할 수 있습니다.

명확성을 위해 CdMetaListResponse 또는 CdMetaResponse[] 형태의 사용을 권장합니다.

src/pages/mypage/ui/customize/step1/components/TrackItem.tsx (3)

78-87: CSS :has 사용은 파이어폭스 등 일부 브라우저에서 미지원 — 폴백 추가 권장

선택자 :has(input)은 지원이 불완전합니다. @supports로 가드하고 폴백을 제공해 주세요.

다음과 같이 적용을 권장합니다.

-  & > div > div:has(input) {
-    border: 1px solid ${({ theme }) => theme.COLOR['gray-700']};
-    background-color: ${({ theme }) => theme.COLOR['gray-800']};
-    padding-right: 40px;
-  }
+  @supports selector(:has(*)) {
+    & > div > div:has(input) {
+      border: 1px solid ${({ theme }) => theme.COLOR['gray-700']};
+      background-color: ${({ theme }) => theme.COLOR['gray-800']};
+      padding-right: 40px;
+    }
+  }
+  @supports not selector(:has(*)) {
+    & input {
+      border: 1px solid ${({ theme }) => theme.COLOR['gray-700']};
+      background-color: ${({ theme }) => theme.COLOR['gray-800']};
+      padding-right: 40px;
+    }
+  }

41-44: alt 인덱스 off-by-one 가능성

orderIndex가 0부터라면 접근성 문구가 0번째로 표시될 수 있습니다. +1 보정 권장.

-  alt={`${track.orderIndex}번째 트랙 썸네일`}
+  alt={`${track.orderIndex + 1}번째 트랙 썸네일`}

31-31: 읽기 전용 입력 UX

URL 복사를 고려하면 disabled 대신 readOnly가 낫습니다(포커싱/복사 가능). 드래그 충돌 우려 시 tabIndex={-1} 병행 가능.

- <Input type="url" value={track.link} disabled />
+ <Input type="url" value={track.link} readOnly tabIndex={-1} />
src/entities/playlist/model/useMyCd.ts (2)

44-47: CD 삭제 후 관련 쿼리 무효화 누락

삭제 성공 시 내 목록/좋아요 목록/해당 트랙리스트 캐시를 무효화해야 UI가 즉시 반영됩니다.

 const deleteMutation = useMutation({
   mutationKey: ['deleteMyCd', cdId],
   mutationFn: () => deleteMyCd(cdId),
+  onSuccess: () => {
+    queryClient.invalidateQueries({ queryKey: ['myCdList'] })
+    queryClient.invalidateQueries({ queryKey: ['myLikeList'] })
+    queryClient.invalidateQueries({ queryKey: ['getTracklist', cdId] })
+  },
 })

50-56: 공개 토글 후 목록 반영

공개여부는 카드 메타에도 반영되므로 목록 쿼리도 무효화하세요.

 const togglePublicMutation = useMutation({
   mutationKey: ['patchMyCdPublic', cdId],
   mutationFn: () => patchMyCdPublic(cdId),
   onSuccess: () => {
     queryClient.invalidateQueries({ queryKey: ['getTracklist', cdId] })
+    queryClient.invalidateQueries({ queryKey: ['myCdList'] })
   },
 })
src/pages/mypage/ui/customize/step2/index.tsx (2)

784-786: 터치 디폴트 방지 이벤트 리스너 add 누락

cleanup만 있고 add가 없습니다. 실제 등록하거나, 불필요하면 cleanup을 제거하세요. 등록하는 쪽을 권장합니다(passive: false).

   // 모바일 Safari 등에서 터치 스크롤 간섭 방지
   const stopDefault = (ev: TouchEvent) => ev.preventDefault()
+  canvas.addEventListener('touchstart', stopDefault, { passive: false })
+  canvas.addEventListener('touchmove', stopDefault, { passive: false })
+  canvas.addEventListener('touchend', stopDefault, { passive: false })

   return () => {
     canvas.removeEventListener('touchstart', stopDefault)
     canvas.removeEventListener('touchmove', stopDefault)
     canvas.removeEventListener('touchend', stopDefault)
   }

Also applies to: 855-860


231-233: 중복 캐시 무효화 제거 제안

useUserSticker의 onSuccess에서 이미 ['getUserStickers']를 invalidate하고 있어 여기서 한 번 더 호출할 필요가 없습니다.

- // 유저 스티커 리스트 재조회
- queryClient.invalidateQueries({ queryKey: ['getUserStickers'] })
src/features/customize/types/customize.ts (1)

5-11: 타입 일관성 점검(선택)

YoutubeVideoInfo.duration은 string, 응답 songs.youtubeLength는 number로 정의되어 있습니다. 양방향 변환 지점에서 혼동이 있을 수 있으니, 저장/표시 레이어에서 명확히 변환/주석을 남겨 두는 것을 권장합니다.

Also applies to: 26-37

src/features/customize/model/useCustomize.ts (1)

38-53: 유튜브 링크 검증 로직 강화 권장

API가 valid 필드를 제공한다면, 유효하지 않은 링크가 포함될 때 즉시 에러로 처리하세요. 또한 링크 문자열 비교 대신 순서(index) 기반 매칭 폴백을 두면 안정성이 올라갑니다.

- const newVideoInfoList = tempSaveMap.youTubeVideoInfo.map((e) => {
-   const resMap = videoResArr.find((res) => e.link === res.link)
+ const invalid = videoResArr.filter((v) => v.valid === false)
+ if (invalid.length) throw new Error('유효하지 않은 유튜브 링크가 포함되어 있습니다.')
+ const newVideoInfoList = tempSaveMap.youTubeVideoInfo.map((e, i) => {
+   const resMap = videoResArr.find((res) => e.link === res.link) ?? videoResArr[i]
    if (!resMap) throw new Error('일치하는 유튜브 영상 정보를 찾지 못했습니다.')
    const { duration, thumbnailUrl, title } = resMap
    return { ...e, duration, thumbnailUrl, title }
 })

API 문서 기준으로 valid 필드 의미/스키마가 확정인지 확인 부탁드립니다.

src/entities/playlist/api/playlist.ts (2)

11-18: query 파라미터는 config.params로 전달해 인코딩/타입 안정성을 확보해주세요

문자열 보간은 공백/특수문자에서 취약합니다. axios의 params를 사용하면 자동 인코딩됩니다.

아래처럼 수정 제안:

-export const getMyCdList = (sort: string) => {
-  return api.get<CdMetaResponse>(`/main/playlist/mypage/me?sort=${sort}`)
-}
+export const getMyCdList = (sort: string) => {
+  return api.get<CdMetaResponse>('/main/playlist/mypage/me', { params: { sort } })
+}

-export const getLikedCdList = (sort: string) => {
-  return api.get<CdMetaResponse>(`/main/playlist/mypage/me/likes?sort=${sort}`)
-}
+export const getLikedCdList = (sort: string) => {
+  return api.get<CdMetaResponse>('/main/playlist/mypage/me/likes', { params: { sort } })
+}

또한 sort를 리터럴 유니온 타입(예: 'RECENT' | 'POPULAR')으로 좁히면 오타 방지에 도움이 됩니다. As per coding guidelines.


41-43: 네이밍 일관성: playlist ↔ tracklist 혼재 정리 제안

주석은 “트랙리스트 상세 조회”인데 함수명은 getPlaylistDetail, 파라미터는 cdId입니다. 추후 혼동을 줄이려면 getTracklistDetail 또는 getCdDetail 등으로 통일을 검토해주세요.

변경 영향 범위가 넓을 수 있어 리네임 시 호출부 일괄 변경 여부 확인 부탁드립니다.

src/pages/mypage/ui/customize/step1/index.tsx (4)

31-35: setter 네이밍 일관성(setBasicInfo)으로 가독성 개선

React 관례상 state setter는 PascalCase를 따릅니다. setbasicInfosetBasicInfo로 교체를 권장합니다.

적용 예:

-const [basicInfoMap, setbasicInfo] = useState({
+const [basicInfoMap, setBasicInfo] = useState({
...
-  setbasicInfo((prev) => ({ ...prev, genre }))
+  setBasicInfo((prev) => ({ ...prev, genre }))
...
-  onChange={(e) => setbasicInfo((prev) => ({ ...prev, name: e.target.value }))}
+  onChange={(e) => setBasicInfo((prev) => ({ ...prev, name: e.target.value }))}
...
-  setIsOn={(isOn) => setbasicInfo((prev) => ({ ...prev, isPublic: isOn }))}
+  setIsOn={(isOn) => setBasicInfo((prev) => ({ ...prev, isPublic: isOn }))}

As per coding guidelines.

Also applies to: 96-98, 238-239, 248-250


180-187: 재정렬 시 안전 가드 추가

prev.find(...)!로 단언 중이라 예외 입력이 오면 런타임 오류 위험이 있습니다. 안전 가드를 추가해 주세요.

예:

-  const onReSort = (newOrderIds: string[]) => {
-    setTracklist((prev) =>
-      newOrderIds.map((id, idx) => {
-        const track = prev.find((t) => t.id === id)!
-        return { ...track, orderIndex: idx + 1 }
-      })
-    )
-  }
+  const onReSort = (newOrderIds: string[]) => {
+    setTracklist((prev) =>
+      newOrderIds
+        .map((id, idx) => {
+          const track = prev.find((t) => t.id === id)
+          return track ? { ...track, orderIndex: idx + 1 } : null
+        })
+        .filter((v): v is NonNullable<typeof v> => !!v)
+    )
+  }

43-49: 상수는 컴포넌트 외부로 이동하여 재생성 방지

MAX_LINK_COUNT, VALID_YOUTUBE_URLS는 불변이므로 컴포넌트 바깥으로 올리면 렌더마다 재생성되지 않습니다.


484-493: :has() 의존 CSS는 브라우저 호환성 점검 필요

:has()는 최신 브라우저에서만 안정적입니다. Input 컴포넌트가 변경되면 구조 의존성도 깨질 수 있습니다. 가능하면 wrapper에 명시적 클래스/prop을 부여해 스타일링하거나, 폴리필/대체 셀렉터 전략을 준비해주세요.

호환성 타깃 브라우저에서 정상 동작 확인 부탁드립니다.

src/shared/ui/Cd.tsx (2)

29-35: responsive 초기 렌더에서 ratio=0로 스티커가 잠깐 사라질 수 있음

dynamicBase 초기값 0 → ratio=0이라 스티커 위치/사이즈가 0으로 계산됩니다. 첫 페인트 깜빡임을 줄이려면 초기 fallback을 두세요.

예:

-const [dynamicBase, setDynamicBase] = useState(0)
-const baseSize = variant === 'responsive' ? dynamicBase : sizeMap[variant].base
-const ratio = baseSize / 275
+const [dynamicBase, setDynamicBase] = useState<number | null>(null)
+const baseSize = variant === 'responsive' ? (dynamicBase ?? sizeMap.md.base) : sizeMap[variant].base
+const ratio = baseSize / 275

또는 variant==='responsive' && dynamicBase===null인 동안 스티커 렌더를 잠시 스킵하는 방법도 있습니다.

Also applies to: 68-79, 158-165


175-186: 비공개 오버레이는 absolute로 깔아 완전 덮도록

현재 PrivateCover가 relative라 stacking 문맥에 따라 겹침이 보장되지 않을 수 있습니다. absolute + inset으로 확실히 덮어주세요.

예:

-const PrivateCover = styled.div`
-  position: relative;
+const PrivateCover = styled.div`
+  position: absolute;
+  inset: 0;
   ${flexColCenter}
   gap: 3px;
   width: 100%;
   height: 100%;
   background-color: rgba(42, 47, 57, 0.7);
src/pages/mypage/ui/customize/index.tsx (1)

28-31: 파생 값은 state 대신 변수로 계산하여 단순화

isEditModecdId에서 파생되므로 state로 가질 필요가 없습니다. 또한 useMyCdActions 호출부에서 그대로 활용하면 의도가 명확해집니다.

예:

-const [currentStep, setCurrentStep] = useState<CUSTOMIZE_STEP>(1)
-const [currentCdId, setCurrentCdId] = useState<number | null>(null)
-const [isEditMode] = useState<boolean>(!!cdId && Number(cdId) > 0)
+const [currentStep, setCurrentStep] = useState<CUSTOMIZE_STEP>(1)
+const [currentCdId, setCurrentCdId] = useState<number | null>(null)
+const isEditMode = typeof cdId === 'number' && cdId > 0

-} = useMyCdActions(isEditMode ? Number(cdId) : -1)
+} = useMyCdActions(isEditMode ? Number(cdId) : -1)

렌더/재계산 시 불필요한 state 업데이트를 줄입니다. As per coding guidelines.

Also applies to: 48-53, 64-85

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ab2a4e and 54e92cc.

⛔ Files ignored due to path filters (11)
  • src/assets/icons/icn_cancel_circle.svg is excluded by !**/*.svg
  • src/assets/icons/icn_drag.svg is excluded by !**/*.svg
  • src/assets/icons/icn_eye.svg is excluded by !**/*.svg
  • src/assets/icons/icn_eye_slash.svg is excluded by !**/*.svg
  • src/assets/icons/icn_eye_slash_white.svg is excluded by !**/*.svg
  • src/assets/icons/icn_link_primary.svg is excluded by !**/*.svg
  • src/assets/icons/icn_no_like.svg is excluded by !**/*.svg
  • src/assets/icons/icn_pin.svg is excluded by !**/*.svg
  • src/assets/icons/icn_pin_primary.svg is excluded by !**/*.svg
  • src/assets/icons/icn_plus_black.svg is excluded by !**/*.svg
  • src/assets/icons/icn_start_black.svg is excluded by !**/*.svg
📒 Files selected for processing (54)
  • src/App.tsx (2 hunks)
  • src/app/providers/ToastProvider.tsx (1 hunks)
  • src/assets/icons/index.ts (2 hunks)
  • src/entities/playlist/api/playlist.ts (2 hunks)
  • src/entities/playlist/model/useMyCd.ts (1 hunks)
  • src/entities/playlist/model/useMyPlaylist.ts (0 hunks)
  • src/entities/playlist/model/usePlaylists.ts (1 hunks)
  • src/entities/playlist/types/playlist.ts (2 hunks)
  • src/features/customize/api/customize.ts (1 hunks)
  • src/features/customize/model/useCustomize.ts (1 hunks)
  • src/features/customize/types/customize.ts (1 hunks)
  • src/features/share/api/share.ts (0 hunks)
  • src/features/share/model/useShare.ts (0 hunks)
  • src/features/share/ui/ShareButton.tsx (3 hunks)
  • src/pages/home/index.tsx (3 hunks)
  • src/pages/myPage/ui/components/CdGrid.tsx (0 hunks)
  • src/pages/myPage/ui/components/index.ts (0 hunks)
  • src/pages/myPage/ui/customize/step1/components/InputLinkItem.tsx (0 hunks)
  • src/pages/myPage/ui/customize/step1/components/index.ts (0 hunks)
  • src/pages/myPage/ui/customize/step1/index.tsx (0 hunks)
  • src/pages/myPage/ui/index.tsx (0 hunks)
  • src/pages/myPage/ui/playlist/index.tsx (0 hunks)
  • src/pages/myPage/ui/setting/components/index.tsx (0 hunks)
  • src/pages/mycd/index.tsx (1 hunks)
  • src/pages/mypage/lib/customizeTheme.ts (1 hunks)
  • src/pages/mypage/types/mypage.ts (1 hunks)
  • src/pages/mypage/ui/MypageLayout.tsx (1 hunks)
  • src/pages/mypage/ui/components/StepHeader.tsx (3 hunks)
  • src/pages/mypage/ui/components/ToggleSwitch.tsx (2 hunks)
  • src/pages/mypage/ui/components/index.ts (1 hunks)
  • src/pages/mypage/ui/customize/index.tsx (5 hunks)
  • src/pages/mypage/ui/customize/step1/components/TrackItem.tsx (1 hunks)
  • src/pages/mypage/ui/customize/step1/components/index.ts (1 hunks)
  • src/pages/mypage/ui/customize/step1/index.tsx (1 hunks)
  • src/pages/mypage/ui/customize/step2/index.tsx (11 hunks)
  • src/pages/mypage/ui/customize/step3/index.tsx (2 hunks)
  • src/pages/mypage/ui/main/components/CdNameInfo.tsx (1 hunks)
  • src/pages/mypage/ui/main/components/MyCdList.tsx (1 hunks)
  • src/pages/mypage/ui/main/components/MyLikedCdList.tsx (1 hunks)
  • src/pages/mypage/ui/main/components/index.ts (1 hunks)
  • src/pages/mypage/ui/main/index.tsx (1 hunks)
  • src/pages/mypage/ui/notification/index.tsx (1 hunks)
  • src/pages/mypage/ui/privacy/index.tsx (1 hunks)
  • src/pages/mypage/ui/setting/index.tsx (3 hunks)
  • src/pages/mypage/ui/terms/index.tsx (1 hunks)
  • src/pages/mypage/ui/tracklist/index.tsx (1 hunks)
  • src/pages/mypage/ui/unregister/index.tsx (1 hunks)
  • src/shared/config/routesConfig.ts (2 hunks)
  • src/shared/lib/useCopyCdShareUrl.ts (1 hunks)
  • src/shared/styles/globalStyle.ts (2 hunks)
  • src/shared/ui/Cd.tsx (7 hunks)
  • src/shared/ui/Input.tsx (6 hunks)
  • src/shared/ui/Modal.tsx (4 hunks)
  • src/stories/ToggleSwitch.stories.tsx (1 hunks)
💤 Files with no reviewable changes (11)
  • src/features/share/model/useShare.ts
  • src/pages/myPage/ui/components/index.ts
  • src/pages/myPage/ui/playlist/index.tsx
  • src/pages/myPage/ui/setting/components/index.tsx
  • src/pages/myPage/ui/index.tsx
  • src/features/share/api/share.ts
  • src/pages/myPage/ui/customize/step1/index.tsx
  • src/pages/myPage/ui/components/CdGrid.tsx
  • src/pages/myPage/ui/customize/step1/components/InputLinkItem.tsx
  • src/pages/myPage/ui/customize/step1/components/index.ts
  • src/entities/playlist/model/useMyPlaylist.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션

포맷팅

  • .prettierrc 설정에 따라 포맷팅 확인
  • 들여쓰기: 2칸 스페이스
  • 최대 줄 길이: 100자
  • 세미콜론 사용 안함
  • 따옴표: 작은따옴표 사용
  • 괄호 안 공백: 있음
  • 화살표 함수 괄호: 항상 사용
  • 줄바꿈: LF 사용

네이밍 컨벤션

  • 컴포넌트: PascalCase (예: UserProfile)
  • 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
  • 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
  • 이미지 파일: kebab-case (예: user-profile-icon.png)

주석 사용

  • 복잡한 로직에만 주석 추가
  • 불필요한 주석 지양 (코드로 설명 가능한 것)
  • TODO/FIXME 형식: // TODO: 설명 - 작성자

가독성

  • 매직 넘버 지양, 의미있는 상수 사용
  • 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
  • 중첩 깊이 최소화 (3단계 이하 권장)

2. React 모범 사례

컴포넌트 작성

  • 최신 React hooks 사용 권장
  • 컴포넌트는 단일 책임 원칙 준수
  • Presentational/Container 컴포넌트 분리
  • 성능 최적화: memo, useCallback, useMemo 적절히 사용
  • 대용량 리스트는 가상화 라이브러리 사용 고려

상태 관리

  • Zustand와 Tanstack Query를 일관되게 사용
  • 상태 구조는 정규화된 형태로 관리
  • 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용

3. 스타일링

Styled Components

  • Styled Components 일관되게 사용
  • 스타일드 컴포넌트명은 의미있게 작성
  • 동적 스타일링은 props나 CSS 변수 활용
  • 테마 시스템 활용하여 글로벌 스타일 관리
  • 재사용 가능한 스타일은 mixin이나 확장으로 관리
  • CSS 포맷팅 가독성 유지
  • 사용하지 않는 스타일이나 중복 스타일 제거

4. Vite 및 빌드 최적화

  • 모듈 import 최적화 (tree-shaking 고려)
  • 환경변수는 .env 파일로 관리
  • vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)

5. 아키텍처 및 개발 환경

폴더 구조

  • Feature-Sliced Design (FSD) 구조 준수
  • 레이어별 참조 규칙 엄격히 적용

타입스크립트

  • strict 모드 사용
  • 타입 명시적으로 작성 (any 사용 지양)
  • path alias (@/_) 절대 경로 import 사용

Git 훅

  • Husky 설정으로 pre-commit, commit-msg 린팅 확인

6. 기타 가이드라인

  • 충분한 근거와 함께 리뷰 제공
  • 정보 검증 후 답변
  • 간결하고 명확한 응답
  • 필요시 추가 컨텍스트 요청
  • 검증되지 않은 주장 지양
  • 가능한 경우 출처 명시
  • 별도 언급 없으면 JavaScript 기준
  • 한국어로 응답
  • 대부분 브라우저에서 지원하는 ES6+ 기능 활용
  • 코드 리뷰를 통한 유지보수성 향상에 적극 활용

Files:

  • src/pages/mypage/ui/main/components/index.ts
  • src/pages/mypage/ui/customize/step1/components/index.ts
  • src/pages/mypage/ui/main/index.tsx
  • src/app/providers/ToastProvider.tsx
  • src/pages/mypage/types/mypage.ts
  • src/pages/mypage/ui/setting/index.tsx
  • src/pages/mypage/ui/privacy/index.tsx
  • src/entities/playlist/model/usePlaylists.ts
  • src/pages/mycd/index.tsx
  • src/pages/mypage/ui/customize/step3/index.tsx
  • src/pages/mypage/ui/notification/index.tsx
  • src/assets/icons/index.ts
  • src/pages/mypage/ui/unregister/index.tsx
  • src/pages/mypage/ui/main/components/MyLikedCdList.tsx
  • src/pages/home/index.tsx
  • src/stories/ToggleSwitch.stories.tsx
  • src/shared/ui/Modal.tsx
  • src/pages/mypage/ui/main/components/MyCdList.tsx
  • src/pages/mypage/lib/customizeTheme.ts
  • src/entities/playlist/model/useMyCd.ts
  • src/pages/mypage/ui/customize/step1/index.tsx
  • src/pages/mypage/ui/customize/step1/components/TrackItem.tsx
  • src/pages/mypage/ui/components/ToggleSwitch.tsx
  • src/pages/mypage/ui/tracklist/index.tsx
  • src/shared/config/routesConfig.ts
  • src/pages/mypage/ui/terms/index.tsx
  • src/pages/mypage/ui/components/StepHeader.tsx
  • src/pages/mypage/ui/MypageLayout.tsx
  • src/entities/playlist/types/playlist.ts
  • src/pages/mypage/ui/main/components/CdNameInfo.tsx
  • src/shared/ui/Input.tsx
  • src/shared/ui/Cd.tsx
  • src/shared/styles/globalStyle.ts
  • src/App.tsx
  • src/features/share/ui/ShareButton.tsx
  • src/pages/mypage/ui/customize/index.tsx
  • src/shared/lib/useCopyCdShareUrl.ts
  • src/features/customize/model/useCustomize.ts
  • src/pages/mypage/ui/customize/step2/index.tsx
  • src/features/customize/api/customize.ts
  • src/features/customize/types/customize.ts
  • src/entities/playlist/api/playlist.ts
  • src/pages/mypage/ui/components/index.ts
🧠 Learnings (1)
📚 Learning: 2025-08-15T06:26:48.282Z
Learnt from: hansololiviakim
PR: dnd-side-project/dnd-13th-8-frontend#27
File: src/shared/styles/globalStyle.ts:34-50
Timestamp: 2025-08-15T06:26:48.282Z
Learning: In the dnd-13th-8-frontend project, the team uses a centralized approach with the custom Input component (src/shared/ui/Input.tsx) for all input elements, implementing custom focus styling with :focus-within instead of relying on native browser focus styles. This architectural decision allows them to safely use global input outline resets in globalStyle.ts.

Applied to files:

  • src/shared/ui/Input.tsx
🧬 Code graph analysis (22)
src/pages/mypage/ui/main/index.tsx (4)
src/shared/lib/useSingleSelect.ts (1)
  • useSingleSelect (3-11)
src/pages/mypage/types/mypage.ts (1)
  • MYPAGE_TAB_TYPE (3-3)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/shared/styles/mixins.ts (1)
  • flexRowCenter (3-8)
src/pages/mypage/ui/setting/index.tsx (2)
src/shared/styles/mixins.ts (1)
  • flexRowCenter (3-8)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/entities/playlist/model/usePlaylists.ts (1)
src/entities/playlist/api/playlist.ts (1)
  • getPlaylistDetail (41-43)
src/pages/mypage/ui/customize/step3/index.tsx (1)
src/features/customize/model/useCustomize.ts (1)
  • useFinalCdCustom (19-26)
src/pages/mypage/ui/main/components/MyLikedCdList.tsx (5)
src/shared/lib/useSingleSelect.ts (1)
  • useSingleSelect (3-11)
src/shared/ui/ContentHeader.tsx (1)
  • SortType (8-8)
src/entities/playlist/model/useMyCd.ts (1)
  • useMyLikedCdList (20-26)
src/shared/styles/mixins.ts (1)
  • flexColCenter (10-15)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/shared/ui/Modal.tsx (2)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/shared/styles/mixins.ts (1)
  • flexRowCenter (3-8)
src/pages/mypage/ui/main/components/MyCdList.tsx (5)
src/features/auth/store/authStore.ts (1)
  • useAuthStore (7-52)
src/shared/lib/useSingleSelect.ts (1)
  • useSingleSelect (3-11)
src/shared/ui/ContentHeader.tsx (1)
  • SortType (8-8)
src/entities/playlist/model/useMyCd.ts (1)
  • useMyCdList (12-18)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/entities/playlist/model/useMyCd.ts (1)
src/entities/playlist/api/playlist.ts (6)
  • getMyCdList (11-13)
  • getLikedCdList (16-18)
  • getTracklist (21-23)
  • deleteMyCd (31-33)
  • patchMyCdPublic (26-28)
  • getMyRepresentativePlaylist (61-63)
src/pages/mypage/ui/customize/step1/index.tsx (6)
src/pages/mypage/ui/customize/index.tsx (1)
  • CustomizeStepProps (13-21)
src/features/customize/model/useCustomize.ts (1)
  • useCdTempSave (28-62)
src/shared/config/musicGenres.ts (2)
  • MUSIC_GENRES (1-12)
  • MusicGenre (17-17)
src/features/customize/types/customize.ts (1)
  • YoutubeVideoInfo (5-11)
src/shared/ui/Modal.tsx (1)
  • ModalProps (9-19)
src/shared/styles/mixins.ts (2)
  • flexColCenter (10-15)
  • flexRowCenter (3-8)
src/pages/mypage/ui/customize/step1/components/TrackItem.tsx (3)
src/features/customize/types/customize.ts (1)
  • YoutubeVideoInfo (5-11)
src/shared/styles/mixins.ts (1)
  • flexRowCenter (3-8)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/pages/mypage/ui/tracklist/index.tsx (8)
src/shared/ui/Modal.tsx (1)
  • ModalProps (9-19)
src/entities/playlist/model/usePlaylists.ts (1)
  • usePlaylistDetail (45-51)
src/entities/playlist/model/useMyCd.ts (1)
  • useMyCdActions (28-65)
src/shared/lib/useCopyCdShareUrl.ts (1)
  • useCopyCdShareUrl (5-37)
src/features/auth/store/authStore.ts (1)
  • useAuthStore (7-52)
src/app/providers/ToastProvider.tsx (1)
  • useToast (44-48)
src/shared/config/musicGenres.ts (1)
  • MUSIC_GENRES (1-12)
src/shared/styles/mixins.ts (1)
  • flexRowCenter (3-8)
src/pages/mypage/ui/components/StepHeader.tsx (2)
src/pages/mypage/types/mypage.ts (1)
  • CUSTOMIZE_STEP (1-1)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/pages/mypage/ui/main/components/CdNameInfo.tsx (1)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/shared/ui/Input.tsx (1)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/shared/ui/Cd.tsx (3)
src/entities/playlist/types/playlist.ts (1)
  • CdCustomData (10-22)
src/shared/styles/mixins.ts (1)
  • flexColCenter (10-15)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/features/share/ui/ShareButton.tsx (1)
src/shared/lib/useCopyCdShareUrl.ts (1)
  • useCopyCdShareUrl (5-37)
src/pages/mypage/ui/customize/index.tsx (3)
src/pages/mypage/types/mypage.ts (1)
  • CUSTOMIZE_STEP (1-1)
src/shared/ui/Modal.tsx (1)
  • ModalProps (9-19)
src/entities/playlist/model/useMyCd.ts (1)
  • useMyCdActions (28-65)
src/features/customize/model/useCustomize.ts (2)
src/features/customize/api/customize.ts (5)
  • getFinalCdCustom (35-37)
  • postYouTubeVideoInfo (15-17)
  • postCdTempSave (20-22)
  • postCdFinalCreate (25-27)
  • postCdFinalUpdate (30-32)
src/features/customize/types/customize.ts (3)
  • CdTempSavePayload (13-18)
  • CdFinalCreatePayload (20-20)
  • CdFinalUpdatePayload (22-22)
src/pages/mypage/ui/customize/step2/index.tsx (4)
src/pages/mypage/ui/customize/index.tsx (1)
  • CustomizeStepProps (13-21)
src/features/customize/model/useCustomize.ts (2)
  • useUserSticker (87-116)
  • useCdFinalSave (64-85)
src/shared/ui/Modal.tsx (1)
  • ModalProps (9-19)
src/pages/mypage/types/mypage.ts (2)
  • StickerInfoType (19-31)
  • StickerThemeType (5-15)
src/features/customize/api/customize.ts (2)
src/shared/api/httpClient.ts (1)
  • api (5-19)
src/features/customize/types/customize.ts (7)
  • YoutubeVideoInfo (5-11)
  • CdTempSavePayload (13-18)
  • CdFinalCreatePayload (20-20)
  • CdFinalSaveResponse (26-37)
  • CdFinalUpdatePayload (22-22)
  • CdCustomResponse (24-24)
  • UserStickerListResponse (45-47)
src/features/customize/types/customize.ts (2)
src/shared/config/musicGenres.ts (1)
  • MusicGenreId (15-15)
src/entities/playlist/types/playlist.ts (1)
  • CdCoverInfo (24-26)
src/entities/playlist/api/playlist.ts (2)
src/shared/api/httpClient.ts (1)
  • api (5-19)
src/entities/playlist/types/playlist.ts (4)
  • CdMetaResponse (28-28)
  • PlaylistDetailResponse (58-62)
  • PlaylistParams (91-95)
  • PlaylistResponse (84-89)
🪛 Biome (2.1.2)
src/pages/mypage/ui/main/components/MyLikedCdList.tsx

[error] 10-10: Do not shadow the global "Error" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

src/pages/mypage/ui/main/components/MyCdList.tsx

[error] 10-10: Do not shadow the global "Error" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🔇 Additional comments (52)
src/App.tsx (1)

98-137: 접근성 개선이 적절합니다.

장식 요소들을 aria-hidden="true" 속성을 가진 div로 감싼 것은 좋은 접근성 개선입니다. 이를 통해 스크린 리더 사용자에게 불필요한 장식 콘텐츠가 전달되지 않습니다.

추가된 DOM 노드는 내부 요소들이 모두 absolute positioning을 사용하기 때문에 레이아웃에 영향을 주지 않으며, 접근성 향상이라는 명확한 목적이 있어 정당화됩니다.

src/pages/home/index.tsx (1)

7-7: LGTM! 불필요한 import 제거 완료

알림 기능 제거에 따라 Notification import를 정리한 것이 적절합니다.

src/shared/styles/globalStyle.ts (1)

42-44: LGTM: 포맷팅 정리

all: unset 포맷팅이 일관성 있게 수정되었습니다.

src/pages/mycd/index.tsx (1)

8-8: LGTM!

import 경로가 모듈 재구성에 따라 올바르게 업데이트되었습니다. 훅 사용 방식은 동일하게 유지되어 기존 동작이 보존됩니다.

src/pages/mypage/ui/notification/index.tsx (1)

4-4: LGTM!

import 경로가 소문자 mypage 네임스페이스로 올바르게 업데이트되었습니다.

src/pages/mypage/ui/terms/index.tsx (1)

1-1: LGTM!

import 경로가 소문자 mypage 네임스페이스로 올바르게 업데이트되었습니다.

src/pages/mypage/ui/privacy/index.tsx (1)

1-1: LGTM!

import 경로가 소문자 mypage 네임스페이스로 올바르게 업데이트되었습니다.

src/pages/mypage/ui/unregister/index.tsx (1)

5-5: LGTM!

import 경로가 소문자 mypage 네임스페이스로 올바르게 업데이트되었습니다.

src/pages/mypage/ui/MypageLayout.tsx (1)

3-7: LGTM!

컴포넌트명이 파일명과 일치하도록 변경되었으며, 프로젝트 전반의 myPage → mypage 네이밍 통일 작업에 부합합니다.

src/app/providers/ToastProvider.tsx (2)

7-7: LGTM!

CD 삭제 기능을 위한 새 토스트 타입이 추가되었습니다.


15-15: LGTM!

토스트 메시지가 한국어로 명확하게 작성되었으며, 다른 메시지들과 형식이 일관됩니다.

src/pages/mypage/ui/setting/index.tsx (1)

8-9: LGTM!

import 경로 변경이 프로젝트의 폴더 구조 재구성 목적에 부합하며, flexRowCenter mixin 추가도 적절합니다.

src/shared/ui/Input.tsx (2)

31-32: LGTM!

requireddisabled props 추가가 적절하며, HTML 표준 속성을 올바르게 전달하고 있습니다. 기본값을 false로 설정한 것도 합리적입니다.

Also applies to: 51-52, 72-73


84-84: LGTM!

InputWrapposition: relative를 추가하고, ErrorMessage를 절대 위치로 배치하는 방식은 에러 메시지를 input 하단에 일관되게 표시하는 표준 패턴입니다.

Also applies to: 125-127

src/stories/ToggleSwitch.stories.tsx (1)

5-5: LGTM!

import 경로 변경이 프로젝트의 폴더명 통일 목적에 부합합니다.

src/assets/icons/index.ts (1)

12-12: LGTM!

새로운 아이콘 추가(CancelCircle, PlusBlack, StartBlack, Eye 관련, NoLike, LinkPrimary)가 PR의 UI 변경사항(비공개 CD UI, 나의 좋아요 등)을 적절히 반영하고 있습니다.

Also applies to: 20-20, 28-28, 45-45, 50-53

src/pages/mypage/lib/customizeTheme.ts (1)

1-1: LGTM!

타입 import 경로 변경이 프로젝트의 폴더명 통일과 일치합니다.

src/pages/mypage/ui/customize/step1/components/index.ts (1)

1-1: LGTM!

barrel export 패턴을 사용하여 TrackItem 컴포넌트를 외부에서 쉽게 import할 수 있도록 했습니다.

src/entities/playlist/model/usePlaylists.ts (1)

45-50: LGTM!

usePlaylistDetailenabled 옵션을 추가하여 쿼리 실행을 조건부로 제어할 수 있게 했습니다. 기본값을 true로 설정하여 기존 동작을 유지하므로 breaking change가 없습니다. 이는 Tanstack Query의 표준 패턴을 따르는 좋은 개선사항입니다.

src/features/share/ui/ShareButton.tsx (1)

12-12: LGTM!

CD 공유 URL 복사 로직을 useCopyCdShareUrl hook으로 분리한 것은 훌륭한 리팩토링입니다. 코드 재사용성이 향상되고, 관심사의 분리 원칙을 잘 따르고 있습니다.

Also applies to: 25-25, 88-90

src/pages/mypage/ui/components/index.ts (1)

1-4: LGTM! 깔끔한 barrel export 구조입니다.

컴포넌트들을 중앙에서 관리하는 깔끔한 구조입니다. import 경로를 단순화하여 개발자 경험을 개선합니다.

src/shared/config/routesConfig.ts (2)

7-15: 일관된 네이밍 변경이 잘 적용되었습니다.

myPagemypage로의 디렉토리명 변경이 모든 import 경로에 일관되게 반영되어 있습니다.


81-81: URL 네이밍 통일이 적절합니다.

:id/playlist:id/tracklist로 변경되어 PR 목표인 URL 네이밍 통일이 달성되었습니다.

src/shared/ui/Modal.tsx (3)

12-12: Optional prop 패턴이 올바르게 적용되었습니다.

TypeScript의 optional property를 활용하여 하위 호환성을 유지하면서 새로운 기능을 추가했습니다.

Also applies to: 24-24


35-38: 레이아웃 구조 변경이 적절합니다.

title과 description을 감싸는 wrapper div를 추가하여 레이아웃을 명확하게 구분했습니다. 조건부 렌더링도 간결하게 처리되었습니다.


83-89: 테마 시스템을 일관되게 활용했습니다.

색상과 폰트 스타일이 테마 시스템을 통해 관리되고 있어 유지보수성이 좋습니다.

src/pages/mypage/ui/main/index.tsx (2)

30-36: TODO 주석이 명확합니다.

알림 기능이 2차 스프린트로 이동한 사유가 명확하게 기록되어 있습니다.


79-96: Transient props 사용이 올바릅니다.

Styled components의 transient props($isActive)를 사용하여 DOM에 불필요한 속성이 전달되지 않도록 했습니다.

src/pages/mypage/ui/main/components/CdNameInfo.tsx (2)

3-10: 간결한 Presentational 컴포넌트입니다.

컴포넌트가 단일 책임 원칙을 잘 따르고 있습니다. inline 타입 정의도 이 정도 크기의 컴포넌트에는 적절합니다.


21-30: 텍스트 overflow 처리가 적절합니다.

-webkit-line-clamp를 사용한 2줄 말줄임 처리와 word-break: break-word를 통한 긴 단어 처리가 잘 구현되었습니다.

src/pages/mypage/ui/customize/step3/index.tsx (2)

21-24: 캐시 무효화 로직이 적절합니다.

tracklist 페이지로 이동하기 전에 캐시를 무효화하여 최신 데이터를 보장합니다. React Query의 베스트 프랙티스를 잘 따르고 있습니다.


10-10: 훅 네이밍 변경이 일관되게 적용되었습니다.

useCdCustomDatauseFinalCdCustom으로 변경되어 API 리팩토링이 반영되었습니다.

src/shared/lib/useCopyCdShareUrl.ts (2)

12-29: 클립보드 API fallback 처리가 우수합니다.

모던 브라우저의 navigator.clipboard.writeText와 레거시 브라우저를 위한 execCommand fallback을 모두 구현했습니다. Safari와 모바일 브라우저 호환성을 고려한 좋은 접근입니다.


8-34: useCallback 사용이 적절합니다.

toast 의존성을 명시하고 useCallback으로 함수를 메모이제이션하여 불필요한 재생성을 방지했습니다.

src/pages/mypage/ui/components/ToggleSwitch.tsx (2)

23-26: 이벤트 핸들러 구현이 올바릅니다.

클릭과 키보드(Enter, Space) 이벤트 모두 처리하여 접근성을 고려했습니다.


13-13: 검증 완료: ToggleSwitch 사용처 모두 새 시그니처와 호환

ToggleSwitch 컴포넌트의 모든 사용처를 확인한 결과, 새로운 시그니처 setIsOn: (value: boolean) => void과 완전히 호환됩니다:

  • src/pages/mypage/ui/setting/index.tsx:24 (주석 처리): 상태 setter 직접 전달
  • src/pages/mypage/ui/customize/step1/index.tsx:249: boolean을 받아 void를 반환하는 함수 전달 ✓
  • src/stories/ToggleSwitch.stories.tsx:31: 상태 setter 직접 전달

기존 updater 패턴 (prev) => !prev 형식을 사용하는 곳은 없으며, 모든 사용처가 이미 새 시그니처에 맞춰져 있습니다.

src/pages/mypage/ui/main/components/index.ts (1)

1-4: 배럴 export 구조가 깔끔합니다.

컴포넌트들을 단일 진입점으로 재export하는 표준 패턴을 잘 따르고 있습니다.

src/pages/mypage/ui/main/components/MyLikedCdList.tsx (4)

10-10: 정적 분석 경고는 무시해도 됩니다.

Biome가 Error를 전역 객체 섀도잉으로 플래그했으나, 이는 명명된 import이므로 실제 문제가 없습니다.


13-21: 로딩 및 에러 처리 로직이 적절합니다.

early return 패턴으로 로딩/에러 상태를 깔끔하게 처리하고 있습니다.


41-54: 하드코딩된 네비게이션 경로를 확인하세요.

Line 44에서 모든 CD를 클릭 시 동일한 /mycd 경로로 이동합니다. 각 CD의 고유 ID를 사용하지 않고 있는데, 이것이 의도된 동작인지 확인이 필요합니다. 일반적으로는 /mycd/${item.playlistId} 같은 형태가 예상됩니다.

TODO 주석에서 병합 후 작업 예정이라고 명시되어 있으므로, 현재는 임시 구현일 수 있습니다.


65-77: 반응형 그리드 레이아웃 구현이 좋습니다.

3열 그리드를 calc를 사용해 정확하게 계산하고 있으며, gap을 상수로 관리하는 것도 좋은 접근입니다.

src/pages/mypage/ui/main/components/MyCdList.tsx (2)

10-10: 정적 분석 경고는 무시해도 됩니다.

MyLikedCdList와 동일하게, Error는 명명된 import이므로 실제 문제가 없습니다.


38-53: 네비게이션 로직이 올바릅니다.

각 CD 클릭 시 해당 playlistId를 사용하여 트랙리스트로 이동하고, state를 통해 출처를 전달하는 패턴이 적절합니다.

src/pages/mypage/ui/tracklist/index.tsx (2)

36-41: 조건부 데이터 페칭 패턴이 효율적입니다.

isFromMyCdList 상태에 따라 적절한 데이터 소스를 사용하고, enabled 옵션으로 불필요한 쿼리를 방지하고 있습니다.


52-82: CD 삭제 로직과 에러 처리가 잘 구현되어 있습니다.

모달을 통한 확인 절차, mutation 성공/실패 처리, 토스트 메시지 표시까지 전체 플로우가 적절합니다.

src/pages/mypage/ui/components/StepHeader.tsx (2)

6-6: 타입 변경이 일관되게 적용되었습니다.

CustomizeStep에서 CUSTOMIZE_STEP로의 마이그레이션이 props, 내부 로직, 타입 캐스팅에 모두 올바르게 반영되어 있습니다. Line 25의 가드 조건 덕분에 Line 29의 타입 캐스팅도 안전합니다.

Also applies to: 16-17, 29-29


94-95: UI 스타일 개선이 적용되었습니다.

버튼에 패딩과 레이블 폰트 스타일이 추가되어 일관된 디자인을 유지하고 있습니다.

src/entities/playlist/types/playlist.ts (3)

3-8: CdBasicInfo 인터페이스가 명확하게 정의되었습니다.

MyCdInfo에서 리네이밍되었고, isRepresentative 필드가 제거되고 isPublic으로 대체되어 PR 목표와 일치합니다.


10-22: CdCustomData의 선택적 필드들이 적절합니다.

cdItemId, theme, imageUrl을 선택적으로 만들어 생성/수정 시나리오에서 유연성을 제공합니다. zCoordinate 추가로 3D 배치가 가능해졌습니다.


36-43: Track 인터페이스에 orderIndex가 추가되었습니다.

트랙 순서를 명시적으로 관리할 수 있게 되어, 정렬 로직이 간단해졌습니다. 이는 tracklist 컴포넌트에서 활용되고 있습니다.

src/pages/mypage/ui/customize/step1/components/TrackItem.tsx (1)

19-25: Reorder.Item value 키 일치 여부 확인

value={track.id}를 사용 중인데, 상위 Reorder.Group의 values 배열 원소와 동일 기준(문자열 id)인지 확인이 필요합니다. 객체를 재정렬한다면 value로 객체 자체를 넘기는 패턴이 더 안전합니다.

상위 컴포넌트에서 Reorder.Group의 values 정의와 onReorder 로직이 id 기준으로 동작하는지 확인 부탁드립니다.

src/features/customize/api/customize.ts (1)

15-17: 응답 타입 스키마 검증 필요: valid 필드 불일치 확인됨

검색 결과, valid 필드는 API 함수의 반환 타입에만 지정되어 있고 실제 코드에서는 전혀 사용되지 않고 있습니다.

  • postYouTubeVideoInfo 호출 처(src/features/customize/model/useCustomize.ts:38)에서 응답을 videoResArr로 받지만, 오직 .length 체크만 수행
  • YoutubeVideoInfo 기본 타입 정의(src/features/customize/types/customize.ts:5-9)에 valid 필드 없음
  • 다른 사용처(src/pages/mypage/ui/customize/step1/index.tsx:36)에서도 상태 타입을 (YoutubeVideoInfo & { id: string })[]로 정의 → valid 미포함

런타임 리스크:

  1. 서버가 valid 필드를 반환하지 않으면 타입 에러 발생 가능
  2. valid: false 항목에 대한 검증 로직 부재 → 잘못된 데이터가 처리될 수 있음

권장 조치:

  • 서버 응답 스키마 확인 후, valid 필드가 실제 반환되는지 검증
  • valid 필드가 필요하다면 응답 처리 시 검증 로직 추가 (예: invalid 항목 필터링)
  • 또는 불필요한 타입 어노테이션 제거

Copy link
Collaborator

@maylh maylh left a comment

Choose a reason for hiding this comment

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

수고하셨습니다 ~~~! 👍👍

svg 아이콘 중에 stroke나 fill 색 때문에 컬러별로 나눠둔 게 있는 것 같은데 (eye_slash),
아이콘 하나만 넣고, 내부 fill이나 stroke는 지운 뒤에 처럼 사용하면 컬러 별로 각각 아이콘 생성하지 않아도 될 거예요 !

Comment on lines +42 to +53
<li key={item.playlistId}>
{/* TODO: 나의CD 이동 후 좋아요한 CD 재생은 각 브랜치 병합 이후 작업 */}
<CdButton type="button" onClick={() => navigate('/mycd')}>
{/* TODO: CD 컴포넌트 좋아요는 각 브랜치 병합 이후 작업 (10/20~) */}
<Cd
variant="responsive"
stickers={item?.cdResponse?.cdItems}
isPublic={item?.isPublic}
/>
</CdButton>
<CdNameInfo title={item?.playlistName || ''} creator={item?.creatorNickname || ''} />
</li>
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 부분이 요거 맞죠 ?
스크린샷 2025-10-23 13 02 14

이거 Playlist 컴포넌트로 만들어두었어요 ! 좋아요 기능도 구현되어 있습니다 Storybook
다만 해당 컴포넌트 사용하실 경우에는 Cd variant랑 onClick route, 일부 css가 조금 달라서, type 같은 props를 받아서 내부에서 조건부 처리는 필요해 보입니다 🥲 .....

@hansololiviakim
Copy link
Member Author

svg 아이콘 중에 stroke나 fill 색 때문에 컬러별로 나눠둔 게 있는 것 같은데 (eye_slash),
아이콘 하나만 넣고, 내부 fill이나 stroke는 지운 뒤에 처럼 사용하면 컬러 별로 각각 아이콘 생성하지 않아도 될 거예요 !

감사합니다! 오늘 병합 먼저 처리하고 다음 이슈에서 작업해두겠습니다~!

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: 3

♻️ Duplicate comments (3)
src/pages/mypage/ui/customize/index.tsx (1)

48-52: cdId 타입 변환 로직 재확인 필요

라인 52의 Number(cdId)는 cdId가 null일 때 0을 반환하여 의도하지 않은 동작을 유발할 수 있습니다. 라인 25-30에서 제안한 수정사항을 적용하면 이 문제도 함께 해결됩니다.

src/pages/mypage/ui/customize/step1/components/TrackItem.tsx (1)

90-93: 중복 선언 제거 확인 — LGTM.

position 중복 선언 이슈가 해소되었습니다. 스타일 정상입니다.

src/pages/mypage/ui/customize/step1/index.tsx (1)

145-152: Modal 닫기 로직 중복 — 동일하게 캐스팅 제거 권장.

위와 동일 이슈이므로 closeModal 재사용으로 통일하세요.

🧹 Nitpick comments (14)
src/pages/mypage/ui/setting/index.tsx (1)

15-20: 로그아웃 핸들러에 에러 처리 및 세션 정리를 추가하는 것을 권장합니다.

현재 구현은 기본 기능을 수행하지만, 다음 개선 사항을 고려해보세요:

  1. setLogin에서 sessionStorage.removeItem('anonymous_token')을 수행하므로, 로그아웃 시에도 세션 스토리지 정리가 필요할 수 있습니다.
  2. 에러 처리 및 사용자 피드백(로딩 상태, 성공/실패 메시지)을 추가하면 더 나은 UX를 제공할 수 있습니다.

다음과 같이 개선할 수 있습니다:

  const onLogoutClick = () => {
+   try {
+     sessionStorage.clear() // 또는 특정 키만 제거
      setLogout()
      navigate('/', { replace: true })
+   } catch (error) {
+     console.error('로그아웃 실패:', error)
+     // 에러 토스트 메시지 표시 등
+   }
  }
src/pages/mypage/ui/unregister/index.tsx (3)

20-30: Optional: 초기 Modal 상태의 description 값 고려

descriptionModalProps에서 optional로 정의되어 있으므로, 빈 문자열 대신 undefined를 사용하는 것이 더 명확할 수 있습니다.

  const [modal, setModal] = useState<ModalProps>({
    isOpen: false,
    title: '',
-   description: '',
+   description: undefined,
    ctaType: 'single',

50-51: 프로덕션 환경의 에러 로깅 개선 권장

console.error를 직접 사용하는 것보다 에러 모니터링 서비스(예: Sentry)나 중앙화된 로깅 시스템을 사용하는 것이 프로덕션 환경에서 더 적합합니다.


68-101: 약관 내용을 별도 상수로 분리 권장

약관 텍스트가 컴포넌트 내부에 하드코딩되어 있어 향후 내용 수정이나 다국어 지원 시 관리가 어려울 수 있습니다. 별도 상수 파일이나 CMS로 분리하는 것을 권장합니다.

예시 구조:

// constants/terms.ts
export const UNREGISTER_TERMS = {
  privacy: {
    title: '1. 개인정보처리방침',
    items: [
      '이용자의 '동의를 기반으로...',
      // ...
    ]
  },
  // ...
}
src/pages/mypage/ui/customize/step1/components/TrackItem.tsx (3)

27-29: 드래그 핸들 a11y 강화 (키보드/스크린리더 지원).

pointer 기반만으로 드래그 시작은 키보드 접근이 어렵습니다. 최소한 핸들에 라벨과 포커스 스타일을 추가해주세요.

-<DragHandle as={motion.button} type="button" onPointerDown={(e) => controls.start(e)}>
+<DragHandle
+  as={motion.button}
+  type="button"
+  aria-label="트랙 순서 변경"
+  onPointerDown={(e) => controls.start(e)}
+>

31-31: URL 입력 필드: disabled 대신 readOnly 권장.

disabled는 탭 포커스/복사 불가합니다. 읽기 전용이면 readOnly로 두고 접근성을 유지하세요.

-<Input type="url" value={track.link} disabled />
+<Input type="url" value={track.link} readOnly aria-label="트랙 링크" />

78-82: :has() 선택자 호환성 확인 및 결합도 낮추기.

:has(input)는 최신 브라우저에서만 동작합니다. 지원 브라우저 범위를 확인하고, 가능하면 Input 래퍼에 클래스/prop(예: $withRightPadding)로 스타일을 제어해 DOM 구조 결합도를 낮추세요.

src/pages/mypage/ui/customize/step1/index.tsx (7)

31-35: Setter 오타(sestBasicInfoMap) — 일관된 네이밍으로 수정.

카멜 케이스/가독성 측면에서 최소 오타 수정은 필수입니다. (추가로 basicInfoMapbasicInfo 리네임은 후속 권장)

-const [basicInfoMap, sestBasicInfoMap] = useState({
+const [basicInfoMap, setBasicInfoMap] = useState({

아래 참조 사용처도 함께 변경 필요:

  • Line 95, 241, 252 등 sestBasicInfoMapsetBasicInfoMap 치환.

57-60: non‑null 단언(genre!) 제거로 타입 안정성 확보.

실행 경로상 유효하더라도 !는 취약합니다. 로컬 변수로 안전하게 처리하세요.

-  tempSaveMap: {
-    ...basicInfoMap,
-    genre: basicInfoMap.genre!.id,
-    youTubeVideoInfo: tracklist.map(({ id: _id, ...rest }) => rest),
-  },
+  const genreId = basicInfoMap.genre?.id
+  if (!genreId) return
+  tempSaveMap: {
+    ...basicInfoMap,
+    genre: genreId,
+    youTubeVideoInfo: tracklist.map(({ id: _id, ...rest }) => rest),
+  },

300-301: 입력 도중 trim은 UX 저하. 추가 시점에만 trim.

onChange에서 trim하면 커서 점프/붙여넣기 이슈가 납니다. 값 추가 직전에만 trim 하세요.

-onChange={(e) => setCurrentTrackUrl(e.target.value.trim())}
+onChange={(e) => setCurrentTrackUrl(e.target.value)}

그리고 onTrackAddClick 시작부에:

const url = currentTrackUrl.trim()
if (!url) return
const thumbnailUrl = await getValidYoutubeThumbnail(url)

68-79: Modal 타입 강제 캐스팅 제거.

setModal({ isOpen: false } as ModalProps)는 런타임 불일치 숨김 위험. partial 허용이 아니라면 안전한 클로저/머지 방식으로 닫으세요.

const closeModal = () => setModal((prev) => ({ ...prev, isOpen: false }))
// 사용처: onClose/onConfirm에서 closeModal 전달

118-139: 썸네일 프리로드 중 언마운트/경합 처리.

이미지 로드 완료 전에 언마운트되면 state 업데이트 경고가 날 수 있습니다. 취소 플래그나 Abort 시그널로 방어하세요.

- setIsThumbnailLoading(true)
- const thumbnailUrl = `https://img.youtube.com/vi/${videoId}/default.jpg`
- const isLoaded = await new Promise<boolean>((resolve) => {
-   const img = new Image()
-   img.onload = () => resolve(true)
-   img.onerror = () => resolve(false)
-   img.src = thumbnailUrl
- })
+ setIsThumbnailLoading(true)
+ let cancelled = false
+ const thumbnailUrl = `https://img.youtube.com/vi/${videoId}/default.jpg`
+ const isLoaded = await new Promise<boolean>((resolve) => {
+   const img = new Image()
+   img.onload = () => resolve(!cancelled)
+   img.onerror = () => resolve(false)
+   img.src = thumbnailUrl
+ })
+ if (cancelled) return ''
...
-} finally {
-  setIsThumbnailLoading(false)
-}
+} finally {
+  if (!cancelled) setIsThumbnailLoading(false)
+}

컴포넌트 외부에 useEffect cleanup에서 cancelled = true 설정하거나, 함수 내부에서 AbortController 패턴으로 개선 가능합니다.


44-48: YouTube 링크 검증: host 기반 파싱으로 오탐 줄이기.

startsWith 대신 URL 파싱 후 host/path로 판별하면 견고합니다.

const ALLOWED_HOSTS = new Set(['youtu.be', 'www.youtube.com', 'music.youtube.com'])
const isAllowed = (() => {
  try {
    const u = new URL(link)
    return ALLOWED_HOSTS.has(u.host) && (!!u.searchParams.get('v') || u.pathname.split('/')[1])
  } catch { return false }
})()

186-188: find(... )! 단언 대신 방어 코드.

이상치로 id 불일치가 들어오면 크래시합니다. 안전하게 처리하세요.

-const track = prev.find((t) => t.id === id)!
-return { ...track, orderIndex: idx + 1 }
+const track = prev.find((t) => t.id === id)
+return track ? { ...track, orderIndex: idx + 1 } : prev[idx]!
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54e92cc and b3f9f68.

📒 Files selected for processing (8)
  • src/features/auth/api/auth.ts (1 hunks)
  • src/features/auth/model/useAuth.ts (2 hunks)
  • src/pages/mypage/ui/customize/index.tsx (5 hunks)
  • src/pages/mypage/ui/customize/step1/components/TrackItem.tsx (1 hunks)
  • src/pages/mypage/ui/customize/step1/index.tsx (1 hunks)
  • src/pages/mypage/ui/setting/index.tsx (4 hunks)
  • src/pages/mypage/ui/unregister/index.tsx (1 hunks)
  • src/stories/Modal.stories.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션

포맷팅

  • .prettierrc 설정에 따라 포맷팅 확인
  • 들여쓰기: 2칸 스페이스
  • 최대 줄 길이: 100자
  • 세미콜론 사용 안함
  • 따옴표: 작은따옴표 사용
  • 괄호 안 공백: 있음
  • 화살표 함수 괄호: 항상 사용
  • 줄바꿈: LF 사용

네이밍 컨벤션

  • 컴포넌트: PascalCase (예: UserProfile)
  • 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
  • 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
  • 이미지 파일: kebab-case (예: user-profile-icon.png)

주석 사용

  • 복잡한 로직에만 주석 추가
  • 불필요한 주석 지양 (코드로 설명 가능한 것)
  • TODO/FIXME 형식: // TODO: 설명 - 작성자

가독성

  • 매직 넘버 지양, 의미있는 상수 사용
  • 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
  • 중첩 깊이 최소화 (3단계 이하 권장)

2. React 모범 사례

컴포넌트 작성

  • 최신 React hooks 사용 권장
  • 컴포넌트는 단일 책임 원칙 준수
  • Presentational/Container 컴포넌트 분리
  • 성능 최적화: memo, useCallback, useMemo 적절히 사용
  • 대용량 리스트는 가상화 라이브러리 사용 고려

상태 관리

  • Zustand와 Tanstack Query를 일관되게 사용
  • 상태 구조는 정규화된 형태로 관리
  • 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용

3. 스타일링

Styled Components

  • Styled Components 일관되게 사용
  • 스타일드 컴포넌트명은 의미있게 작성
  • 동적 스타일링은 props나 CSS 변수 활용
  • 테마 시스템 활용하여 글로벌 스타일 관리
  • 재사용 가능한 스타일은 mixin이나 확장으로 관리
  • CSS 포맷팅 가독성 유지
  • 사용하지 않는 스타일이나 중복 스타일 제거

4. Vite 및 빌드 최적화

  • 모듈 import 최적화 (tree-shaking 고려)
  • 환경변수는 .env 파일로 관리
  • vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)

5. 아키텍처 및 개발 환경

폴더 구조

  • Feature-Sliced Design (FSD) 구조 준수
  • 레이어별 참조 규칙 엄격히 적용

타입스크립트

  • strict 모드 사용
  • 타입 명시적으로 작성 (any 사용 지양)
  • path alias (@/_) 절대 경로 import 사용

Git 훅

  • Husky 설정으로 pre-commit, commit-msg 린팅 확인

6. 기타 가이드라인

  • 충분한 근거와 함께 리뷰 제공
  • 정보 검증 후 답변
  • 간결하고 명확한 응답
  • 필요시 추가 컨텍스트 요청
  • 검증되지 않은 주장 지양
  • 가능한 경우 출처 명시
  • 별도 언급 없으면 JavaScript 기준
  • 한국어로 응답
  • 대부분 브라우저에서 지원하는 ES6+ 기능 활용
  • 코드 리뷰를 통한 유지보수성 향상에 적극 활용

Files:

  • src/features/auth/api/auth.ts
  • src/pages/mypage/ui/customize/step1/components/TrackItem.tsx
  • src/features/auth/model/useAuth.ts
  • src/stories/Modal.stories.tsx
  • src/pages/mypage/ui/setting/index.tsx
  • src/pages/mypage/ui/unregister/index.tsx
  • src/pages/mypage/ui/customize/index.tsx
  • src/pages/mypage/ui/customize/step1/index.tsx
🧬 Code graph analysis (7)
src/features/auth/api/auth.ts (1)
src/shared/api/httpClient.ts (1)
  • api (5-19)
src/pages/mypage/ui/customize/step1/components/TrackItem.tsx (3)
src/features/customize/types/customize.ts (1)
  • YoutubeVideoInfo (5-11)
src/shared/styles/mixins.ts (1)
  • flexRowCenter (3-8)
src/shared/styles/theme.ts (1)
  • theme (3-116)
src/features/auth/model/useAuth.ts (1)
src/features/auth/api/auth.ts (2)
  • getUserInfo (20-22)
  • deleteAccount (15-17)
src/pages/mypage/ui/setting/index.tsx (2)
src/features/auth/store/authStore.ts (1)
  • useAuthStore (7-52)
src/shared/styles/mixins.ts (1)
  • flexRowCenter (3-8)
src/pages/mypage/ui/unregister/index.tsx (5)
src/features/auth/model/useAuth.ts (1)
  • useDeleteAccount (27-32)
src/features/auth/store/authStore.ts (1)
  • useAuthStore (7-52)
src/shared/ui/Modal.tsx (1)
  • ModalProps (9-19)
src/shared/styles/terms.ts (2)
  • TermsContainer (5-12)
  • TermsItems (14-19)
src/shared/styles/mixins.ts (1)
  • flexRowCenter (3-8)
src/pages/mypage/ui/customize/index.tsx (4)
src/pages/mypage/types/mypage.ts (1)
  • CUSTOMIZE_STEP (1-1)
src/shared/ui/Modal.tsx (1)
  • ModalProps (9-19)
src/entities/playlist/types/playlist.ts (1)
  • PlaylistDetailResponse (58-62)
src/entities/playlist/model/useMyCd.ts (1)
  • useMyCdActions (28-65)
src/pages/mypage/ui/customize/step1/index.tsx (6)
src/pages/mypage/ui/customize/index.tsx (1)
  • CustomizeStepProps (13-21)
src/features/customize/model/useCustomize.ts (1)
  • useCdTempSave (28-62)
src/shared/config/musicGenres.ts (2)
  • MUSIC_GENRES (1-12)
  • MusicGenre (17-17)
src/features/customize/types/customize.ts (1)
  • YoutubeVideoInfo (5-11)
src/shared/ui/Modal.tsx (1)
  • ModalProps (9-19)
src/shared/styles/mixins.ts (2)
  • flexColCenter (10-15)
  • flexRowCenter (3-8)
🔇 Additional comments (13)
src/pages/mypage/ui/setting/index.tsx (4)

8-10: LGTM! 임포트 경로가 올바르게 업데이트되었습니다.

새로운 기능에 필요한 임포트가 추가되었고, 경로 네이밍 변경 사항(myPage → mypage)이 올바르게 반영되었습니다.


29-33: LGTM! 알림 기능이 적절하게 주석 처리되었습니다.

PR 설명과 일치하게 2차 스프린트로 이관될 알림 기능이 TODO 주석과 함께 명확하게 표시되어 있습니다.


67-74: 이전 리뷰에서 지적된 로그아웃 버튼 이슈가 해결되었습니다.

로그아웃 버튼에 onLogoutClick 핸들러가 올바르게 연결되었고, 탈퇴하기 버튼도 정상적으로 동작합니다. 코드 변경 사항이 좋습니다!


113-130: LGTM! 스타일 컴포넌트가 코딩 가이드라인을 잘 따르고 있습니다.

테마 시스템과 mixin을 적절히 활용하고 있으며, CSS 포맷팅이 가독성 있게 작성되었습니다.

src/features/auth/api/auth.ts (1)

14-17: LGTM!

회원 탈퇴 API 함수가 올바르게 구현되었습니다. 타입 명시와 주석이 적절하며, 기존 API 함수들과 일관된 패턴을 따르고 있습니다.

src/features/auth/model/useAuth.ts (1)

3-3: LGTM!

회원 탈퇴 mutation hook이 올바르게 구현되었습니다. 기존 hook들과 일관된 패턴을 따르고 있으며, 구조가 명확합니다.

Also applies to: 27-32

src/pages/mypage/ui/unregister/index.tsx (1)

126-133: 절대 위치 지정 시 반응형 동작 확인 권장

BottomCraWrap이 절대 위치(position: absolute)와 고정된 bottom: 34px 값을 사용합니다. 다양한 화면 크기나 모바일 키보드 표시 시 의도한 대로 동작하는지 확인해주세요.

src/pages/mypage/ui/customize/index.tsx (4)

2-11: LGTM: import 구조가 명확합니다

절대 경로를 사용한 import 구조가 깔끔하고, 필요한 타입과 훅이 모두 올바르게 import되었습니다.


13-21: LGTM: Props 타입 정의가 명확합니다

CUSTOMIZE_STEP 타입과 prevTracklist 네이밍 변경이 일관성 있게 적용되었습니다.


32-46: LGTM: Modal 상태에 description 필드가 올바르게 추가되었습니다

Modal 컴포넌트의 새로운 description prop과 일관되게 상태가 업데이트되었습니다.


54-100: LGTM: 에러/로딩 핸들링 및 렌더링 로직이 적절합니다

에러 발생 시 /error로 이동하고, 로딩 중에는 Loading 컴포넌트를 표시하는 로직이 올바르게 구현되었습니다. 각 Step 컴포넌트에 prevTracklist가 일관되게 전달되고, Modal에도 description prop이 올바르게 전달되었습니다.

src/pages/mypage/ui/customize/step1/index.tsx (2)

59-60: ESLint disable 제거 대응 — 잘 하셨습니다.

{ id: _id, ...rest }로 미사용 필드 명시 제거가 컨벤션에 부합합니다. LGTM.


326-334: Reorder 제어 방식 LGTM.

values={ids} + Reorder.Item value={id} 조합이 일관되고 onReorder 처리도 적절합니다.

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: 4

♻️ Duplicate comments (3)
src/pages/mypage/ui/customize/index.tsx (2)

25-30: cdId 추출 및 타입 안전성 문제 (이전 리뷰 미해결)

이전 리뷰에서 지적된 location.state 타입 안전성 문제가 여전히 남아있습니다:

  1. location.state가 객체이지만 cdId 속성이 없는 경우 cdId: undefined가 됩니다
  2. Line 30의 Number(cdId)cdIdnull일 때 0을 반환합니다
  3. Line 52에서 Number(cdId)를 사용할 때 null0 변환으로 인해 의도하지 않은 동작이 발생할 수 있습니다

As per coding guidelines.

다음과 같이 수정하세요:

  const navigate = useNavigate()
  const location = useLocation()
- const { cdId } = location?.state ?? { cdId: null }
+ const cdId = (location.state as { cdId?: number } | null)?.cdId ?? null
 
  const [currentStep, setCurrentStep] = useState<CUSTOMIZE_STEP>(1)
  const [currentCdId, setCurrentCdId] = useState<number | null>(null)
- const [isEditMode] = useState<boolean>(!!cdId && Number(cdId) > 0)
+ const [isEditMode] = useState<boolean>(typeof cdId === 'number' && cdId > 0)

48-52: Number(cdId) 사용 시 null 처리 문제

cdIdnull일 때 Number(null)0을 반환하여 의도하지 않은 동작을 일으킬 수 있습니다. useMyCdActions에 잘못된 ID가 전달될 수 있습니다.

다음과 같이 수정하세요:

  const {
    tracklist: prevTracklist,
    isLoading,
    isError,
- } = useMyCdActions(isEditMode ? Number(cdId) : -1)
+ } = useMyCdActions(isEditMode && typeof cdId === 'number' ? cdId : -1)
src/pages/mypage/ui/tracklist/index.tsx (1)

100-101: 에러 처리에서 return 문이 누락되었습니다.

Line 101에서 /error로 이동한 후 return 문이 없어, 컴포넌트가 계속 렌더링을 시도할 수 있습니다. 이는 예상치 못한 동작이나 에러를 발생시킬 수 있습니다.

다음과 같이 수정하세요:

  if (isLoading) return <Loading isLoading={isLoading} />
- if (isError) return navigate('/error')
+ if (isError) {
+   navigate('/error')
+   return null
+ }
🧹 Nitpick comments (1)
src/pages/mypage/ui/tracklist/index.tsx (1)

36-41: 조건부 데이터 페칭 로직에 설명 주석을 추가하는 것을 권장합니다.

isFromMyCdList 플래그에 따라 서로 다른 훅을 사용하는 이유가 명확하지 않을 수 있습니다. 향후 유지보수를 위해 간단한 주석을 추가하면 좋겠습니다.

+ // 진입 경로에 따라 다른 데이터 소스 사용: 내 CD 목록에서 진입 시 useMyCdActions, 일반 플레이리스트 상세에서 진입 시 usePlaylistDetail
  const isFromMyCdList = state?.isFromMyCdList === true

  const { data, isLoading, isError } = usePlaylistDetail(Number(cdId), { enabled: !isFromMyCdList })
  const { tracklist, deleteMutation, togglePublicMutation } = useMyCdActions(Number(cdId), {
    enabled: isFromMyCdList,
  })
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3f9f68 and 98c7894.

📒 Files selected for processing (4)
  • index.html (1 hunks)
  • src/pages/mypage/ui/customize/index.tsx (5 hunks)
  • src/pages/mypage/ui/customize/step3/index.tsx (2 hunks)
  • src/pages/mypage/ui/tracklist/index.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션

포맷팅

  • .prettierrc 설정에 따라 포맷팅 확인
  • 들여쓰기: 2칸 스페이스
  • 최대 줄 길이: 100자
  • 세미콜론 사용 안함
  • 따옴표: 작은따옴표 사용
  • 괄호 안 공백: 있음
  • 화살표 함수 괄호: 항상 사용
  • 줄바꿈: LF 사용

네이밍 컨벤션

  • 컴포넌트: PascalCase (예: UserProfile)
  • 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
  • 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
  • 이미지 파일: kebab-case (예: user-profile-icon.png)

주석 사용

  • 복잡한 로직에만 주석 추가
  • 불필요한 주석 지양 (코드로 설명 가능한 것)
  • TODO/FIXME 형식: // TODO: 설명 - 작성자

가독성

  • 매직 넘버 지양, 의미있는 상수 사용
  • 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
  • 중첩 깊이 최소화 (3단계 이하 권장)

2. React 모범 사례

컴포넌트 작성

  • 최신 React hooks 사용 권장
  • 컴포넌트는 단일 책임 원칙 준수
  • Presentational/Container 컴포넌트 분리
  • 성능 최적화: memo, useCallback, useMemo 적절히 사용
  • 대용량 리스트는 가상화 라이브러리 사용 고려

상태 관리

  • Zustand와 Tanstack Query를 일관되게 사용
  • 상태 구조는 정규화된 형태로 관리
  • 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용

3. 스타일링

Styled Components

  • Styled Components 일관되게 사용
  • 스타일드 컴포넌트명은 의미있게 작성
  • 동적 스타일링은 props나 CSS 변수 활용
  • 테마 시스템 활용하여 글로벌 스타일 관리
  • 재사용 가능한 스타일은 mixin이나 확장으로 관리
  • CSS 포맷팅 가독성 유지
  • 사용하지 않는 스타일이나 중복 스타일 제거

4. Vite 및 빌드 최적화

  • 모듈 import 최적화 (tree-shaking 고려)
  • 환경변수는 .env 파일로 관리
  • vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)

5. 아키텍처 및 개발 환경

폴더 구조

  • Feature-Sliced Design (FSD) 구조 준수
  • 레이어별 참조 규칙 엄격히 적용

타입스크립트

  • strict 모드 사용
  • 타입 명시적으로 작성 (any 사용 지양)
  • path alias (@/_) 절대 경로 import 사용

Git 훅

  • Husky 설정으로 pre-commit, commit-msg 린팅 확인

6. 기타 가이드라인

  • 충분한 근거와 함께 리뷰 제공
  • 정보 검증 후 답변
  • 간결하고 명확한 응답
  • 필요시 추가 컨텍스트 요청
  • 검증되지 않은 주장 지양
  • 가능한 경우 출처 명시
  • 별도 언급 없으면 JavaScript 기준
  • 한국어로 응답
  • 대부분 브라우저에서 지원하는 ES6+ 기능 활용
  • 코드 리뷰를 통한 유지보수성 향상에 적극 활용

Files:

  • src/pages/mypage/ui/customize/step3/index.tsx
  • src/pages/mypage/ui/tracklist/index.tsx
  • src/pages/mypage/ui/customize/index.tsx
🧬 Code graph analysis (3)
src/pages/mypage/ui/customize/step3/index.tsx (1)
src/features/customize/model/useCustomize.ts (1)
  • useFinalCdCustom (19-26)
src/pages/mypage/ui/tracklist/index.tsx (8)
src/shared/ui/Modal.tsx (1)
  • ModalProps (9-19)
src/entities/playlist/model/usePlaylists.ts (1)
  • usePlaylistDetail (45-51)
src/entities/playlist/model/useMyCd.ts (1)
  • useMyCdActions (28-65)
src/shared/lib/useCopyCdShareUrl.ts (1)
  • useCopyCdShareUrl (5-37)
src/features/auth/store/authStore.ts (1)
  • useAuthStore (7-52)
src/app/providers/ToastProvider.tsx (1)
  • useToast (44-48)
src/shared/config/musicGenres.ts (1)
  • MUSIC_GENRES (1-12)
src/shared/styles/mixins.ts (1)
  • flexRowCenter (3-8)
src/pages/mypage/ui/customize/index.tsx (4)
src/pages/mypage/types/mypage.ts (1)
  • CUSTOMIZE_STEP (1-1)
src/shared/ui/Modal.tsx (1)
  • ModalProps (9-19)
src/entities/playlist/types/playlist.ts (1)
  • PlaylistDetailResponse (58-62)
src/entities/playlist/model/useMyCd.ts (1)
  • useMyCdActions (28-65)
🔇 Additional comments (8)
index.html (1)

17-17: 형식 자동 감지 비활성화 메타 태그 추가 — 적절함

전화번호, 이메일, 주소에 대한 브라우저의 자동 형식 감지를 명시적으로 비활성화하는 설정으로, 앱 전체의 형식 표현 제어를 위한 일관된 접근 방식입니다. PR에서 진행 중인 글로벌 스타일 업데이트와도 잘 정렬되어 있습니다.

src/pages/mypage/ui/customize/step3/index.tsx (2)

21-24: LGTM!

캐시 무효화 후 페이지 이동하는 패턴이 적절하게 구현되었습니다. playlistDetail 쿼리 키는 트랙리스트 페이지에서 사용하는 캐시를 갱신하기 위한 것으로 보입니다.


51-51: LGTM!

버튼 클릭 시 캐시 무효화와 네비게이션을 함께 처리하는 moveToTracklist 함수를 적절하게 연결했습니다.

src/pages/mypage/ui/customize/index.tsx (2)

13-21: LGTM!

인터페이스 타입 정의가 새로운 데이터 구조와 타입에 맞게 올바르게 업데이트되었습니다. CUSTOMIZE_STEP 타입과 PlaylistDetailResponse 타입이 적절히 적용되었습니다.


32-46: LGTM!

Modal 상태에 description 필드가 추가되었고, ModalProps 인터페이스의 선택적 필드로 올바르게 초기화되었습니다.

src/pages/mypage/ui/tracklist/index.tsx (3)

52-98: 이벤트 핸들러 로직이 잘 구현되어 있습니다.

  • 삭제 확인 모달 → mutation → 성공/실패 처리 흐름이 명확합니다
  • 공개 토글은 useMyCdActions에서 자동으로 쿼리 무효화를 처리하므로 별도의 성공 핸들러가 없어도 적절합니다
  • 모달 상태 관리가 일관되게 함수형 업데이트를 사용하고 있습니다

126-129: TODO 주석이 존재하며, 모두 재생 기능이 구현 대기 중입니다.

현재 버튼에 onClick 핸들러가 없지만, TODO 주석으로 향후 작업이 문서화되어 있습니다. 병합 후 관련 브랜치와 함께 작업 예정으로 보입니다.

병합 후 해당 기능 구현을 추적할 수 있도록 별도 이슈가 생성되어 있는지 확인해주세요.


184-225: 스타일 컴포넌트가 잘 구성되어 있습니다.

  • 테마 시스템을 일관되게 활용하고 있습니다
  • flexRowCenter 믹스인을 적절히 재사용하고 있습니다
  • 컴포넌트 네이밍이 명확하고 PascalCase를 따릅니다
  • 불필요한 중복 스타일이 없습니다

Based on coding guidelines

@github-actions
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

HIGH 빠르게 처리해야 하는 높은 우선순위

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 마이페이지 UI/API 변경사항 반영

2 participants