[feat] 마이페이지 > 플레이리스트 추가 UI 구현#49
Conversation
- 프로토타입 제출용 임시 하드 코딩, 타 이슈에서 세부 스타일 및 기능 수정 예정
|
Note Other AI code review bot(s) detectedCodeRabbit 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새 아이콘 8종을 아이콘 인덱스에 추가했고, 플레이리스트 생성 페이지(/mypage/create)와 테마 커스터마이즈 페이지(/customize)를 새로 추가하여 라우팅에 연결했습니다. 마이페이지 그리드의 추가 버튼은 생성 페이지로 네비게이트하도록 변경되었고, 플레이리스트 생성은 장르 선택, 제목/링크 입력, 저장 유효성 검사를 포함합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant PG as PlaylistGrid
participant R as Router
participant C as Create Page
U->>PG: "추가" 버튼 클릭
PG->>R: navigate('/mypage/create')
R-->>C: Create 페이지 렌더
U->>C: 장르 선택 / 제목 입력 / 링크 입력
U->>C: 저장 클릭 (유효성 통과)
C->>R: navigate('/mypage')
R-->>U: MyPage 렌더
sequenceDiagram
participant U as User
participant C as Create Page
participant R as Router
participant Z as Customize Page
U->>C: CD 편집 버튼 클릭
C->>R: navigate('/customize')
R-->>Z: Customize 페이지 렌더
U->>Z: 뒤로/저장 클릭
Z->>R: navigate(-1)
R-->>U: 이전 페이지 복귀
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: .coderabbit.yaml 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
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은 마이페이지에 플레이리스트 추가 및 CD 커스텀 기능을 위한 임시 UI를 구현합니다. 사용자가 새로운 플레이리스트를 만들고, CD 디자인을 개인화할 수 있도록 기본적인 인터페이스를 제공하는 것이 목표입니다. 이는 향후 기능 확장을 위한 프로토타입 단계의 작업입니다.
Highlights
- 플레이리스트 생성 UI 구현: 마이페이지 내에서 새로운 플레이리스트를 생성할 수 있는 임시 사용자 인터페이스가 구현되었습니다. 이 UI는 플레이리스트의 장르와 제목을 설정하고, 관련 링크를 추가할 수 있는 기능을 제공합니다.
- CD 커스텀 UI 구현: 플레이리스트 생성 과정에서 CD의 외형을 커스터마이징할 수 있는 임시 UI가 추가되었습니다. 사용자는 다양한 테마를 선택하고 CD 디자인을 미리 볼 수 있습니다.
- 새로운 SVG 아이콘 추가: 새로운 UI 요소들을 지원하기 위해 다양한 SVG 아이콘(예: 다운 화살표, 드래그, 도움말, 연필, 핀, 공유, 휴지통 등)이 프로젝트에 추가되었으며, 이들은
index.ts파일을 통해 쉽게 접근할 수 있도록 내보내졌습니다. - 라우팅 설정 업데이트: 새로 구현된 플레이리스트 생성 및 CD 커스텀 페이지가 애플리케이션의 라우팅 설정에 통합되어, 해당 기능에 접근할 수 있는 경로가 활성화되었습니다.
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 in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| 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 issue 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
-
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. ↩
There was a problem hiding this comment.
Code Review
마이페이지의 플레이리스트 추가 및 커스터마이징 UI 구현에 대한 PR을 리뷰했습니다. 전반적으로 임시 UI 구현이라는 점을 감안하더라도, 코드의 안정성과 유지보수성을 높일 수 있는 몇 가지 개선점을 발견했습니다.
주요 피드백 사항은 다음과 같습니다:
react-router-dom의navigate함수 사용법 오류 수정- 매직 넘버 및 매직 스트링을 테마 변수나 상수로 대체하여 코드 일관성 및 유지보수성 향상
useCallback을 사용한 불필요한 렌더링 방지- CSS의 불안정한 위치 지정 방식 개선
자세한 내용은 각 파일의 리뷰 코멘트를 참고해주세요.
| const ThemeContainer = styled.div` | ||
| margin: 0 -20px; | ||
| width: calc(100% + 40px); | ||
| flex: 1; | ||
| border-radius: 12px 12px 0 0; | ||
| background-color: ${({ theme }) => theme.COLOR['gray-700']}; | ||
| overflow-y: auto; | ||
| padding: 20px; | ||
| ` |
There was a problem hiding this comment.
ThemeContainer를 포함한 여러 스타일 컴포넌트에서 20px, 40px와 같은 매직 넘버를 사용하고 있습니다. 스타일 가이드에 따라 일관성 있는 디자인 시스템을 유지하고 유지보수성을 높이기 위해 테마에 정의된 간격(spacing) 변수를 사용하는 것을 권장합니다.1 예를 들어, theme.SPACING.lg와 같이 사용할 수 있습니다.
Style Guide References
Footnotes
| const onGenreClick = (genre: MusicGenre) => { | ||
| setMetaGenre(genre) | ||
| setIsBottomSheetOpen(false) | ||
| } |
There was a problem hiding this comment.
onGenreClick 함수는 컴포넌트가 렌더링될 때마다 새로 생성됩니다. 이 함수는 BottomSheet 내부의 많은 EachGenre 컴포넌트에 전달되므로, 불필요한 리렌더링을 방지하기 위해 useCallback으로 감싸는 것이 좋습니다. 이는 스타일 가이드에서 권장하는 성능 최적화 방법입니다.1
const onGenreClick = useCallback((genre: MusicGenre) => {
setMetaGenre(genre)
setIsBottomSheetOpen(false)
}, [])Style Guide References
Footnotes
|
🎵 Storybook Link 🎵 |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (11)
src/shared/ui/Button.tsx (1)
29-38: L 사이즈: width: 100% + flex: 1 병행은 의도와 다르게 동작할 수 있음flex: 1은 flex-basis: 0%를 내포해서 width/min/max 제어가 무시되거나 주변 요소까지 밀어낼 수 있습니다. L 사이즈에서 폭 제어를 width/min/max로 일관되게 가져가려면 flex를 끄는 쪽이 안전합니다.
Create/Customize 페이지 컨테이너에서 기대 레이아웃(한 줄 가득 채움, min/max 폭 유지)이 맞는지 한번 확인 부탁드립니다.
L: css` min-width: ${BUTTON_STYLES.L.minWidth}; width: ${BUTTON_STYLES.L.width}; max-width: ${BUTTON_STYLES.L.maxWidth}; padding: ${BUTTON_STYLES.L.padding}; border-radius: ${BUTTON_STYLES.L.borderRadius}; ${({ theme }) => theme.FONT['body1-normal']} - flex: 1; + /* flex-basis: 0을 유발하는 flex: 1 대신 명시적으로 고정 */ + flex: 0 0 auto; `,src/shared/config/musicGenres.ts (1)
1-1: TODO 코멘트 문구/형식 정돈 제안가이드에 맞춰 TODO 형식을 통일하고, 맞춤법(아니여서 → 아니어서)을 보완하면 좋겠습니다.
-// TODO: 장르는 아직 확정 데이터 아니여서 임시로 넣어둠, 차주 변경 예정 +// TODO: 확정 장르 데이터로 교체 (차주 예정) - @hansololiviakimsrc/pages/myPage/ui/components/PlaylistGrid.tsx (1)
30-35: 접근성: 추가 버튼에 aria-label 부여 권장아이콘 버튼은 텍스트가 없어 스크린리더에서 목적이 모호합니다. aria-label을 추가해 주세요. 또한 CdBackground에서
& > button { width/height: 100% }규칙이 있다면 SvgButton의 width/height 프롭이 버튼 박스 크기와 충돌할 수 있어 의도한 것이 맞는지도 확인 부탁드립니다.<SvgButton icon={Plus} width={40} height={40} + aria-label="플레이리스트 추가" onClick={() => navigate('/mypage/create')} />src/shared/config/routesConfig.ts (1)
5-5: 경로 별칭 통일 권장 (@/사용으로 맞춤)현재 파일 내에서
@/pages와@pages가 혼용됩니다. 가이드에 맞춰 절대 경로 별칭을 통일하면 인지부하와 설정 이슈를 줄일 수 있습니다.-const Customize = lazy(() => import('@pages/customize')) +const Customize = lazy(() => import('@/pages/customize')) ... -const Create = lazy(() => import('@pages/myPage/ui/create')) +const Create = lazy(() => import('@/pages/myPage/ui/create'))Also applies to: 7-7
src/pages/customize/index.tsx (3)
63-66: 모바일 사이즈 clamp의 중간값 개선 제안현재 clamp의 중간값이 고정 px(300px)라 반응형 이점이 거의 없습니다. vw 기반 중간값으로 변경하면 다양한 모바일 폭에서 더 자연스러운 크기 조절이 됩니다.
적용 diff:
- width: ${({ $deviceType }) => - $deviceType === 'mobile' ? 'clamp(280px, 300px, 335px)' : '335px'}; - height: ${({ $deviceType }) => - $deviceType === 'mobile' ? 'clamp(280px, 300px, 335px)' : '335px'}; + width: ${({ $deviceType }) => + $deviceType === 'mobile' ? 'clamp(280px, 35vw, 335px)' : '335px'}; + height: ${({ $deviceType }) => + $deviceType === 'mobile' ? 'clamp(280px, 35vw, 335px)' : '335px'};
36-38: 접근성: “테마 추가” 버튼에 aria-label 추가아이콘만 있는 버튼은 스크린 리더에서 의미를 알기 어렵습니다. aria-label을 추가해 주세요.
적용 diff:
- <AddThemeButton> + <AddThemeButton aria-label="테마 추가"> <span>+</span> </AddThemeButton>
100-104: 그리드 반응형 개선 제안고정 4열은 작은 화면에서 아이템이 과도하게 작아질 수 있습니다. auto-fill/minmax로 보다 유연한 레이아웃을 고려해보세요.
적용 diff:
- grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));src/pages/myPage/ui/create/index.tsx (4)
34-36: 불리언이 아닌 값 반환으로 인한 가독성/타입 안전성 저하isValidate가 string | undefined를 반환할 수 있어 의도 파악이 어렵습니다. 명확히 boolean을 반환하도록 개선해 주세요. trim으로 공백 입력도 방지하면 UX가 좋아집니다.
적용 diff:
- const isValidate = () => { - return metaGenre?.id && metaTitle && link - } + const isValidate = (): boolean => { + return Boolean(metaGenre?.id && metaTitle.trim() && link.trim()) + }
74-81: 입력 컨트롤을 비제어에서 제어 컴포넌트로 전환 권장defaultValue를 쓰면 상태(metaTitle)와 DOM 값이 분리될 수 있습니다. value를 사용한 제어 컴포넌트로 통일하면 예측 가능성이 높아집니다.
적용 diff:
- <Input - type="text" - placeholder="플레이리스트명" - defaultValue={metaTitle ?? ''} - maxLength={24} - onChange={(e) => setMetaTitle(e.target.value)} - /> + <Input + type="text" + placeholder="플레이리스트명" + value={metaTitle} + maxLength={24} + onChange={(e) => setMetaTitle(e.target.value)} + />
124-128: URL 입력도 제어 컴포넌트로 통일link 입력 역시 value를 지정해 상태와 동기화해 주세요.
적용 diff:
- <Input - type="url" - placeholder="링크를 입력해주세요" - onChange={(e) => setLink(e.target.value)} - /> + <Input + type="url" + placeholder="링크를 입력해주세요" + value={link} + onChange={(e) => setLink(e.target.value)} + />
176-187: CD 배경 컨테이너 사이즈 명시 및 불필요 자식 선택자 정리CdBackground에 width/height가 없어 부모(124px)에 맞게 확실히 채우지 못할 수 있습니다. 또한 & > button 규칙은 현 구조에서 불필요할 가능성이 큽니다.
적용 diff:
const CdBackground = styled.div` position: relative; ${flexRowCenter} margin-bottom: 10px; - - border-radius: 10px; - background-color: ${({ theme }) => theme.COLOR['gray-600']}; - - & > button { - width: 100%; - height: 100%; - } + width: 100%; + height: 100%; + border-radius: 10px; + background-color: ${({ theme }) => theme.COLOR['gray-600']};
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (8)
src/assets/icons/icn_down_arrow.svgis excluded by!**/*.svgsrc/assets/icons/icn_drag.svgis excluded by!**/*.svgsrc/assets/icons/icn_help_circle.svgis excluded by!**/*.svgsrc/assets/icons/icn_pencil_primary.svgis excluded by!**/*.svgsrc/assets/icons/icn_pin.svgis excluded by!**/*.svgsrc/assets/icons/icn_pin_primary.svgis excluded by!**/*.svgsrc/assets/icons/icn_share.svgis excluded by!**/*.svgsrc/assets/icons/icn_trash.svgis excluded by!**/*.svg
📒 Files selected for processing (7)
src/assets/icons/index.ts(1 hunks)src/pages/customize/index.tsx(1 hunks)src/pages/myPage/ui/components/PlaylistGrid.tsx(2 hunks)src/pages/myPage/ui/create/index.tsx(1 hunks)src/shared/config/musicGenres.ts(1 hunks)src/shared/config/routesConfig.ts(2 hunks)src/shared/ui/Button.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/assets/icons/index.tssrc/shared/ui/Button.tsxsrc/shared/config/routesConfig.tssrc/pages/customize/index.tsxsrc/pages/myPage/ui/components/PlaylistGrid.tsxsrc/pages/myPage/ui/create/index.tsxsrc/shared/config/musicGenres.ts
🧬 Code Graph Analysis (1)
src/pages/customize/index.tsx (2)
src/shared/hooks/useDevice.ts (2)
useDevice(14-17)DeviceType(3-3)src/shared/styles/theme.ts (1)
theme(3-116)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Chromatic
🔇 Additional comments (7)
src/shared/config/musicGenres.ts (1)
14-17: 타입 추출 방식 적절합니다리터럴 좁은 타입에서 파생 타입을 추출하는 패턴이 깔끔하고 재사용성도 좋습니다. 이후 API 연동 시에도 변경 범위를 최소화할 수 있겠습니다.
src/pages/myPage/ui/components/PlaylistGrid.tsx (1)
23-23: 추가 버튼 네비게이션 연결 깔끔합니다SvgButton → /mypage/create 라우팅 연결이 명확하고 의도가 분명합니다. 라우트도 routesConfig에 추가되어 일관됩니다.
Also applies to: 30-35
src/shared/config/routesConfig.ts (1)
37-37: Create 페이지의 nav 노출 정책 확인 요청Customize는 hideNav: true로 설정되어 있으나 Create는 기본값(노출)입니다. 프로토타입 기준 의도된 UX인지 확인 부탁드립니다. 필요 시
hideNav: true를 추가해 주세요.Also applies to: 43-43
src/assets/icons/index.ts (2)
15-26: 아이콘 export 패턴 일관성 좋습니다기존 방식과 동일한 네이밍/패턴으로 신규 아이콘이 추가되어 사용처에서도 혼란이 없겠습니다.
15-26: SVG 자산 및 export 검증 완료
추가된 모든 SVG 파일이src/assets/icons에 존재하며,index.ts에도 정상적으로 export되어 있습니다. 추가 조치가 필요하지 않습니다.src/pages/customize/index.tsx (1)
71-79: 토큰/스타일 사용 일관성과 구조는 좋습니다ThemeContainer 영역의 토큰 활용과 스크롤/라운딩 처리 등이 일관적이며, 프로토타입 목적에 적합합니다.
src/pages/myPage/ui/create/index.tsx (1)
100-116: 도움말 Popover 전환 처리 깔끔합니다AnimatePresence와 motion을 통한 진입/퇴장 애니메이션 처리가 간결하고 의도에 부합합니다.
maylh
left a comment
There was a problem hiding this comment.
수고하셨습니다 ^_^ 다음주는 더 화이팅해보아요 ... 💪🏻💪🏻💪🏻💪🏻
| <SvgButton icon={Share} width={20} height={20} /> | ||
| </LeftActions> | ||
| <RightAction type="button"> | ||
| <Pin /> |
There was a problem hiding this comment.
나중에 대표 플리 설정 여부에 따라 아이콘 바꿔줄 때
<Pin fill={theme.COLOR['primary-normal']} />
로 컬러 입혀주면 Pin, PinPrimary 둘 다 import 하지 않아도 될 것 같습니다 ! (이 경우 icn_pin.svg의 fill 속성 삭제 필요)
|
📢 Resolve 되지 않은 리뷰들은 타 이슈에서 진행 예정입니다! 📢 |
🛰️ 관련 이슈
✨ 주요 변경 사항
1️⃣ 마이페이지 > 플레이리스트 생성 UI 임시 구현
2️⃣ 플레이리스트 CD 커스텀 UI 임시 구현
🔍 테스트 방법 / 체크리스트
🗯️ PR 포인트
프로토타입 제출용 임시 UI만 구현하였습니다.
실제 배포용은 아니며, 세부 디자인, 기능은 다른 이슈에서 수정 및 추가 작업 예정입니다.
🚀 알게된 점
📖 참고 자료 (선택)
🖼️ Demo
ScreenRecording_08-17-2025.20-31-48_1.MP4
Summary by CodeRabbit
New Features
Style
Chores