-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/61 마이페이지 구현(UI 및 API 연동) #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
""" Walkthrough이 변경사항은 "내 정보" 마이페이지 기능의 전체적인 구현을 포함합니다. 사용자 프로필 정보 조회, 수정, 이미지 업로드, 내비게이션, 반응형 라우팅, 상태 관리, 관련 API 라우트 및 타입 정의, SVG 아이콘 컴포넌트 추가 등이 새롭게 도입되었습니다. 모든 주요 마이페이지 하위 페이지와 컴포넌트가 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ProfileNavigation
participant ProfileImage
participant useMyPageStore
participant useMyPageQueries
participant API_Route
participant External_API
User->>ProfileNavigation: 프로필 이미지 업로드 클릭
ProfileNavigation->>ProfileImage: onEdit 호출, 파일 선택
ProfileImage->>ProfileNavigation: 파일 전달
ProfileNavigation->>useMyPageQueries: uploadProfileImage(file)
useMyPageQueries->>API_Route: POST /api/users/me/image
API_Route->>External_API: POST /users/me/image (with token)
External_API-->>API_Route: 업로드 결과 반환
API_Route-->>useMyPageQueries: 업로드 결과 반환
useMyPageQueries->>useMyPageStore: user 상태 업데이트
useMyPageStore-->>ProfileNavigation: 최신 프로필 이미지 반영
sequenceDiagram
participant User
participant ProfilePage
participant useMyPageQueries
participant API_Route
participant External_API
User->>ProfilePage: 닉네임/비밀번호 입력 후 저장 클릭
ProfilePage->>useMyPageQueries: updateMyProfile(data)
useMyPageQueries->>API_Route: PATCH /api/users/me
API_Route->>External_API: PATCH /users/me (with token)
External_API-->>API_Route: 수정 결과 반환
API_Route-->>useMyPageQueries: 수정 결과 반환
useMyPageQueries->>ProfilePage: 결과 반영 및 상태 업데이트
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40분 Suggested reviewers
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ 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. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 30
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (24)
next.config.ts(1 hunks)public/assets/svg/my-activities-dashboard.tsx(1 hunks)public/assets/svg/my-activities.tsx(1 hunks)public/assets/svg/my-reservation.tsx(1 hunks)public/assets/svg/my-user.tsx(1 hunks)public/assets/svg/pen.tsx(1 hunks)public/assets/svg/profile-default.tsx(1 hunks)src/apis/mypage.ts(1 hunks)src/app/(with-header)/mypage/activities/page.tsx(1 hunks)src/app/(with-header)/mypage/components/ProfileImage.tsx(1 hunks)src/app/(with-header)/mypage/components/ProfileNavigation.tsx(1 hunks)src/app/(with-header)/mypage/components/index.ts(1 hunks)src/app/(with-header)/mypage/dashboard/page.tsx(1 hunks)src/app/(with-header)/mypage/layout.tsx(1 hunks)src/app/(with-header)/mypage/page.tsx(1 hunks)src/app/(with-header)/mypage/profile/page.tsx(1 hunks)src/app/(with-header)/mypage/reservations/page.tsx(1 hunks)src/app/api/users/me/image/route.ts(1 hunks)src/app/api/users/me/route.ts(1 hunks)src/hooks/useDebounce.ts(1 hunks)src/hooks/useMyPageQueries.ts(1 hunks)src/hooks/useResponsiveRouting.ts(1 hunks)src/stores/MyPage/useMyPageStore.ts(1 hunks)src/types/mypageTypes.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/app/(with-header)/mypage/activities/page.tsx (1)
src/app/(with-header)/layout.tsx (1)
WithHeaderLayout(4-16)
src/apis/mypage.ts (3)
src/types/user.ts (1)
User(1-8)src/apis/privateInstance.ts (1)
privateInstance(53-53)src/types/mypageTypes.ts (2)
UpdateProfileRequest(36-40)ProfileImageResponse(48-50)
src/types/mypageTypes.ts (1)
src/types/user.ts (1)
User(1-8)
src/app/(with-header)/mypage/components/ProfileImage.tsx (2)
src/types/mypageTypes.ts (1)
ProfileImageProps(4-11)src/components/Avatar.tsx (1)
Avatar(34-86)
🪛 Biome (2.1.2)
public/assets/svg/my-activities.tsx
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/my-reservation.tsx
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/my-user.tsx
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/profile-default.tsx
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/my-activities-dashboard.tsx
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/pen.tsx
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
src/app/(with-header)/mypage/profile/page.tsx
[error] 131-136: A form label must be associated with an input.
Consider adding a for or htmlFor attribute to the label element or moving the input element to inside the label element.
(lint/a11y/noLabelWithoutControl)
[error] 144-151: A form label must be associated with an input.
Consider adding a for or htmlFor attribute to the label element or moving the input element to inside the label element.
(lint/a11y/noLabelWithoutControl)
[error] 159-163: A form label must be associated with an input.
Consider adding a for or htmlFor attribute to the label element or moving the input element to inside the label element.
(lint/a11y/noLabelWithoutControl)
[error] 174-179: A form label must be associated with an input.
Consider adding a for or htmlFor attribute to the label element or moving the input element to inside the label element.
(lint/a11y/noLabelWithoutControl)
🔇 Additional comments (19)
next.config.ts (1)
8-18:port: ''값 검토 필요
remotePatterns에서port는 선택 필드로, 빈 문자열이 아닌undefined생략이 일반적입니다. 일부 Next.js 버전에서는 빈 문자열이 스키마 검증에 걸릴 수 있으니 확인 바랍니다.src/hooks/useDebounce.ts (1)
5-17: 구현 깔끔 – 추가 제안 없음
타이머 정리 로직과 제네릭 반환형이 올바르게 구현되어 있습니다. 좋은 작업입니다.src/hooks/useResponsiveRouting.ts (1)
36-80: 훅의 복잡한 로직이 잘 구현되었습니다.반응형 라우팅의 복잡한 요구사항(무한 리다이렉트 방지, 사용자 네비게이션 감지, 디바운싱)을 잘 처리했습니다. refs를 사용한 상태 관리와 타이밍 제어가 적절하게 구현되어 있습니다.
src/app/(with-header)/mypage/components/index.ts (1)
1-2: 컴포넌트 내보내기가 적절하게 구현되었습니다.ProfileImage와 ProfileNavigation 컴포넌트의 배럴 내보내기가 깔끔하게 구현되어 있습니다. 컴포넌트 구조화가 잘 되어있습니다.
src/app/(with-header)/mypage/dashboard/page.tsx (1)
1-18: 페이지 구조와 스타일링이 잘 구현되었습니다.반응형 디자인과 일관된 스타일링을 적용한 대시보드 페이지의 기본 구조가 잘 만들어졌습니다. 제목과 컨테이너의 레이아웃이 적절합니다.
src/app/(with-header)/mypage/page.tsx (2)
24-33: 디바이스 타입 감지 및 리다이렉트 로직이 적절합니다.mounted 상태를 사용하여 hydration 불일치를 방지하고, 디바이스 타입에 따른 조건부 리다이렉트가 올바르게 구현되어 있습니다.
54-75: 스켈레톤 로딩 UI가 잘 구현되었습니다.로딩 상태와 리다이렉트 중 적절한 스켈레톤 UI를 제공하여 사용자 경험을 개선했습니다.
src/app/(with-header)/mypage/profile/page.tsx (1)
73-103: 폼 검증 로직이 잘 구현되었습니다.저장 전 최종 검증을 수행하고, 조건부로 비밀번호를 업데이트 데이터에 포함하는 로직이 적절합니다.
src/app/api/users/me/route.ts (1)
11-44: GET 핸들러가 적절하게 구현되었습니다.인증 토큰 검증, 외부 API 호출, 에러 처리가 올바르게 구현되어 있습니다.
src/apis/mypage.ts (3)
12-15: 프로필 조회 함수가 올바르게 구현되었습니다.타입 안전성을 보장하며 간단하고 명확한 구현입니다.
21-26: 프로필 업데이트 함수가 적절합니다.요청 타입과 응답 타입이 올바르게 정의되어 있고, PATCH 메서드 사용이 적절합니다.
32-44: 이미지 업로드 함수가 올바르게 구현되었습니다.FormData 구성과 Content-Type 헤더 설정이 적절하며, 파일 업로드를 위한 표준적인 구현입니다.
src/app/(with-header)/mypage/components/ProfileNavigation.tsx (3)
56-76: 파일 업로드 검증 로직이 잘 구현되었습니다.파일 타입과 크기 검증이 적절하며, 5MB 제한은 PR 목표와 일치합니다. 파일 입력 초기화도 올바르게 처리되었습니다.
102-116: 네비게이션 메뉴 구현이 우수합니다.활성 상태 표시, 접근성을 고려한 링크 구조, 적절한 스타일링이 잘 구현되어 있습니다.
91-97: 숨겨진 파일 입력 구현이 적절합니다.표준적인 숨겨진 파일 입력 패턴을 사용하여 커스텀 업로드 버튼을 구현했습니다.
src/types/mypageTypes.ts (2)
1-11: 인터페이스 정의가 적절합니다!ProfileImageProps 인터페이스가 컴포넌트의 요구사항을 잘 반영하고 있으며, 모든 속성이 선택적으로 정의되어 유연성을 제공합니다.
31-57: 요청/응답 인터페이스가 잘 설계되었습니다!API 통신을 위한 타입 정의가 명확하고, 특히 UpdateProfileRequest의 선택적 필드와 ProfileFormData의 비밀번호 확인 필드가 적절히 구성되어 있습니다.
src/app/(with-header)/mypage/components/ProfileImage.tsx (1)
24-33: URL 유효성 검사 함수가 잘 구현되었습니다!URL 생성자를 사용한 검증 방식이 적절하며, 빈 문자열 처리도 올바르게 되어 있습니다.
src/hooks/useMyPageQueries.ts (1)
21-48: 프로필 조회 훅이 잘 구현되었습니다!React Query와 Zustand store의 동기화가 적절하며, 5분의 staleTime 설정도 합리적입니다.
| <svg | ||
| xmlns='http://www.w3.org/2000/svg' | ||
| width={size} | ||
| height={size} | ||
| fill='none' | ||
| viewBox='0 0 24 24' | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
접근성: <title> 또는 aria-label 추가 필요
정적 분석 도구가 지적한 대로 스크린리더가 아이콘 의미를 알 수 있도록 <title> 요소를 추가하거나 role="img" aria-label="대시보드 아이콘" 속성을 부여하세요.
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 24 24'
+ role='img'
+ aria-label='대시보드 아이콘'
>
+ <title>대시보드</title>📝 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.
| <svg | |
| xmlns='http://www.w3.org/2000/svg' | |
| width={size} | |
| height={size} | |
| fill='none' | |
| viewBox='0 0 24 24' | |
| > | |
| <svg | |
| xmlns='http://www.w3.org/2000/svg' | |
| width={size} | |
| height={size} | |
| fill='none' | |
| viewBox='0 0 24 24' | |
| role='img' | |
| aria-label='대시보드 아이콘' | |
| > | |
| <title>대시보드</title> |
🧰 Tools
🪛 Biome (2.1.2)
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
🤖 Prompt for AI Agents
In public/assets/svg/my-activities-dashboard.tsx around lines 4 to 10, the SVG
element lacks accessibility features for screen readers. Add a <title> element
inside the SVG with a descriptive text or add role="img" and aria-label="대시보드
아이콘" attributes to the SVG tag to provide meaningful context for screen readers.
| const MyActivitiesIcon = ({ size = 24, ...props }) => ( | ||
| <svg | ||
| xmlns='http://www.w3.org/2000/svg' | ||
| width={size} | ||
| height={size} | ||
| fill='none' | ||
| viewBox='0 0 24 24' | ||
| > | ||
| <path | ||
| fill='#000' | ||
| d='M12 8a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4m-2 12c-.25 0-.46-.18-.5-.42l-.37-2.65c-.63-.25-1.17-.59-1.69-.99l-2.49 1.01c-.22.08-.49 0-.61-.22l-2-3.46a.493.493 0 0 1 .12-.64l2.11-1.66L4.5 12l.07-1-2.11-1.63a.493.493 0 0 1-.12-.64l2-3.46c.12-.22.39-.31.61-.22l2.49 1c.52-.39 1.06-.73 1.69-.98l.37-2.65c.04-.24.25-.42.5-.42h4c.25 0 .46.18.5.42l.37 2.65c.63.25 1.17.59 1.69.98l2.49-1c.22-.09.49 0 .61.22l2 3.46c.13.22.07.49-.12.64L19.43 11l.07 1-.07 1 2.11 1.63c.19.15.25.42.12.64l-2 3.46c-.12.22-.39.31-.61.22l-2.49-1c-.52.39-1.06.73-1.69.98l-.37 2.65c-.04.24-.25.42-.5.42zm1.25-18-.37 2.61c-1.2.25-2.26.89-3.03 1.78L5.44 7.35l-.75 1.3L6.8 10.2a5.55 5.55 0 0 0 0 3.6l-2.12 1.56.75 1.3 2.43-1.04c.77.88 1.82 1.52 3.01 1.76l.37 2.62h1.52l.37-2.61c1.19-.25 2.24-.89 3.01-1.77l2.43 1.04.75-1.3-2.12-1.55c.4-1.17.4-2.44 0-3.61l2.11-1.55-.75-1.3-2.41 1.04a5.42 5.42 0 0 0-3.03-1.77L12.75 4z' | ||
| /> | ||
| </svg> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
접근성을 위한 aria-label 또는 title 요소를 추가하세요.
SVG 아이콘에 접근성을 위한 대체 텍스트가 누락되어 있습니다. 스크린 리더 사용자를 위해 적절한 설명을 추가해주세요.
const MyActivitiesIcon = ({ size = 24, ...props }) => (
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 24 24'
+ aria-label="내 활동"
+ role="img"
>
<path
fill='#000'
d='M12 8a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4m-2 12c-.25 0-.46-.18-.5-.42l-.37-2.65c-.63-.25-1.17-.59-1.69-.99l-2.49 1.01c-.22.08-.49 0-.61-.22l-2-3.46a.493.493 0 0 1 .12-.64l2.11-1.66L4.5 12l.07-1-2.11-1.63a.493.493 0 0 1-.12-.64l2-3.46c.12-.22.39-.31.61-.22l2.49 1c.52-.39 1.06-.73 1.69-.98l.37-2.65c.04-.24.25-.42.5-.42h4c.25 0 .46.18.5.42l.37 2.65c.63.25 1.17.59 1.69.98l2.49-1c.22-.09.49 0 .61.22l2 3.46c.13.22.07.49-.12.64L19.43 11l.07 1-.07 1 2.11 1.63c.19.15.25.42.12.64l-2 3.46c-.12.22-.39.31-.61.22l-2.49-1c-.52.39-1.06.73-1.69.98l-.37 2.65c-.04.24-.25.42-.5.42zm1.25-18-.37 2.61c-1.2.25-2.26.89-3.03 1.78L5.44 7.35l-.75 1.3L6.8 10.2a5.55 5.55 0 0 0 0 3.6l-2.12 1.56.75 1.3 2.43-1.04c.77.88 1.82 1.52 3.01 1.76l.37 2.62h1.52l.37-2.61c1.19-.25 2.24-.89 3.01-1.77l2.43 1.04.75-1.3-2.12-1.55c.4-1.17.4-2.44 0-3.61l2.11-1.55-.75-1.3-2.41 1.04a5.42 5.42 0 0 0-3.03-1.77L12.75 4z'
/>
</svg>
);📝 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.
| const MyActivitiesIcon = ({ size = 24, ...props }) => ( | |
| <svg | |
| xmlns='http://www.w3.org/2000/svg' | |
| width={size} | |
| height={size} | |
| fill='none' | |
| viewBox='0 0 24 24' | |
| > | |
| <path | |
| fill='#000' | |
| d='M12 8a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4m-2 12c-.25 0-.46-.18-.5-.42l-.37-2.65c-.63-.25-1.17-.59-1.69-.99l-2.49 1.01c-.22.08-.49 0-.61-.22l-2-3.46a.493.493 0 0 1 .12-.64l2.11-1.66L4.5 12l.07-1-2.11-1.63a.493.493 0 0 1-.12-.64l2-3.46c.12-.22.39-.31.61-.22l2.49 1c.52-.39 1.06-.73 1.69-.98l.37-2.65c.04-.24.25-.42.5-.42h4c.25 0 .46.18.5.42l.37 2.65c.63.25 1.17.59 1.69.98l2.49-1c.22-.09.49 0 .61.22l2 3.46c.13.22.07.49-.12.64L19.43 11l.07 1-.07 1 2.11 1.63c.19.15.25.42.12.64l-2 3.46c-.12.22-.39.31-.61.22l-2.49-1c-.52.39-1.06.73-1.69.98l-.37 2.65c-.04.24-.25.42-.5.42zm1.25-18-.37 2.61c-1.2.25-2.26.89-3.03 1.78L5.44 7.35l-.75 1.3L6.8 10.2a5.55 5.55 0 0 0 0 3.6l-2.12 1.56.75 1.3 2.43-1.04c.77.88 1.82 1.52 3.01 1.76l.37 2.62h1.52l.37-2.61c1.19-.25 2.24-.89 3.01-1.77l2.43 1.04.75-1.3-2.12-1.55c.4-1.17.4-2.44 0-3.61l2.11-1.55-.75-1.3-2.41 1.04a5.42 5.42 0 0 0-3.03-1.77L12.75 4z' | |
| /> | |
| </svg> | |
| ); | |
| const MyActivitiesIcon = ({ size = 24, ...props }) => ( | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width={size} | |
| height={size} | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| aria-label="내 활동" | |
| role="img" | |
| > | |
| <path | |
| fill="#000" | |
| d="M12 8a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4m-2 12c-.25 0-.46-.18-.5-.42l-.37-2.65c-.63-.25-1.17-.59-1.69-.99l-2.49 1.01c-.22.08-.49 0-.61-.22l-2-3.46a.493.493 0 0 1 .12-.64l2.11-1.66L4.5 12l.07-1-2.11-1.63a.493.493 0 0 1-.12-.64l2-3.46c.12-.22.39-.31.61-.22l2.49 1c.52-.39 1.06-.73 1.69-.98l.37-2.65c.04-.24.25-.42.5-.42h4c.25 0 .46.18.5.42l.37 2.65c.63.25 1.17.59 1.69.98l2.49-1c.22-.09.49 0 .61.22l2 3.46c.13.22.07.49-.12.64L19.43 11l.07 1-.07 1 2.11 1.63c.19.15.25.42.12.64l-2 3.46c-.12.22-.39.31-.61.22l-2.49-1c-.52.39-1.06.73-1.69.98l-.37 2.65c-.04.24-.25.42-.5.42zm1.25-18-.37 2.61c-1.2.25-2.26.89-3.03 1.78L5.44 7.35l-.75 1.3L6.8 10.2a5.55 5.55 0 0 0 0 3.6l-2.12 1.56.75 1.3 2.43-1.04c.77.88 1.82 1.52 3.01 1.76l.37 2.62h1.52l.37-2.61c1.19-.25 2.24-.89 3.01-1.77l2.43 1.04.75-1.3-2.12-1.55c.4-1.17.4-2.44 0-3.61l2.11-1.55-.75-1.3-2.41 1.04a5.42 5.42 0 0 0-3.03-1.77L12.75 4z" | |
| /> | |
| </svg> | |
| ); |
🧰 Tools
🪛 Biome (2.1.2)
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
🤖 Prompt for AI Agents
In public/assets/svg/my-activities.tsx around lines 3 to 16, the SVG icon lacks
accessibility features such as an aria-label or title element. To fix this, add
a descriptive title element inside the SVG tag or include an aria-label
attribute on the SVG element to provide alternative text for screen readers,
ensuring the icon is accessible.
| const MyReservationIcon = ({ size = 24, ...props }) => ( | ||
| <svg | ||
| xmlns='http://www.w3.org/2000/svg' | ||
| width={size} | ||
| height={size} | ||
| fill='none' | ||
| viewBox='0 0 24 24' | ||
| > | ||
| <path | ||
| fill='#000' | ||
| d='m17 21-2.75-3 1.16-1.16L17 18.43l3.59-3.59 1.16 1.41M12.8 21H5c-1.11 0-2-.89-2-2V5c0-1.11.89-2 2-2h14c1.11 0 2 .89 2 2v7.8c-.61-.35-1.28-.6-2-.72V5H5v14h7.08c.12.72.37 1.39.72 2m-.8-4H7v-2h5m2.68-2H7v-2h10v1.08c-.85.14-1.63.46-2.32.92M17 9H7V7h10' | ||
| /> | ||
| </svg> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
접근성을 위한 aria-label 또는 title 요소를 추가하세요.
MyActivitiesIcon과 동일하게 SVG 아이콘에 접근성을 위한 대체 텍스트가 누락되어 있습니다. 스크린 리더 사용자를 위해 적절한 설명을 추가해주세요.
const MyReservationIcon = ({ size = 24, ...props }) => (
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 24 24'
+ aria-label="내 예약"
+ role="img"
>
<path
fill='#000'
d='m17 21-2.75-3 1.16-1.16L17 18.43l3.59-3.59 1.16 1.41M12.8 21H5c-1.11 0-2-.89-2-2V5c0-1.11.89-2 2-2h14c1.11 0 2 .89 2 2v7.8c-.61-.35-1.28-.6-2-.72V5H5v14h7.08c.12.72.37 1.39.72 2m-.8-4H7v-2h5m2.68-2H7v-2h10v1.08c-.85.14-1.63.46-2.32.92M17 9H7V7h10'
/>
</svg>
);📝 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.
| const MyReservationIcon = ({ size = 24, ...props }) => ( | |
| <svg | |
| xmlns='http://www.w3.org/2000/svg' | |
| width={size} | |
| height={size} | |
| fill='none' | |
| viewBox='0 0 24 24' | |
| > | |
| <path | |
| fill='#000' | |
| d='m17 21-2.75-3 1.16-1.16L17 18.43l3.59-3.59 1.16 1.41M12.8 21H5c-1.11 0-2-.89-2-2V5c0-1.11.89-2 2-2h14c1.11 0 2 .89 2 2v7.8c-.61-.35-1.28-.6-2-.72V5H5v14h7.08c.12.72.37 1.39.72 2m-.8-4H7v-2h5m2.68-2H7v-2h10v1.08c-.85.14-1.63.46-2.32.92M17 9H7V7h10' | |
| /> | |
| </svg> | |
| ); | |
| const MyReservationIcon = ({ size = 24, ...props }) => ( | |
| <svg | |
| xmlns='http://www.w3.org/2000/svg' | |
| width={size} | |
| height={size} | |
| fill='none' | |
| viewBox='0 0 24 24' | |
| aria-label="내 예약" | |
| role="img" | |
| > | |
| <path | |
| fill='#000' | |
| d='m17 21-2.75-3 1.16-1.16L17 18.43l3.59-3.59 1.16 1.41M12.8 21H5c-1.11 0-2-.89-2-2V5c0-1.11.89-2 2-2h14c1.11 0 2 .89 2 2v7.8c-.61-.35-1.28-.6-2-.72V5H5v14h7.08c.12.72.37 1.39.72 2m-.8-4H7v-2h5m2.68-2H7v-2h10v1.08c-.85.14-1.63.46-2.32.92M17 9H7V7h10' | |
| /> | |
| </svg> | |
| ); |
🧰 Tools
🪛 Biome (2.1.2)
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
🤖 Prompt for AI Agents
In public/assets/svg/my-reservation.tsx around lines 3 to 16, the SVG icon lacks
accessibility features such as an aria-label or title element. To fix this, add
a descriptive title element inside the SVG tag or include an aria-label
attribute with appropriate text to provide alternative text for screen readers,
similar to the approach used in MyActivitiesIcon.
| const MyUsersIcon = ({ size = 24, ...props }) => ( | ||
| <svg | ||
| xmlns='http://www.w3.org/2000/svg' | ||
| width={size} | ||
| height={size} | ||
| fill='none' | ||
| viewBox='0 0 24 24' | ||
| > | ||
| <path | ||
| fill='#000' | ||
| d='m21.1 12.5 1.4 1.41-6.53 6.59L12.5 17l1.4-1.41 2.07 2.08zM11 4a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4m0 7c.68 0 1.5.09 2.41.26l-1.67 1.67-.74-.03c-2.97 0-6.1 1.46-6.1 2.1v1.1h6.2L13 20H3v-3c0-2.66 5.33-4 8-4' | ||
| /> | ||
| </svg> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
접근성 문제와 TypeScript 타입 정의를 개선해야 합니다.
정적 분석 도구에서 지적한 접근성 문제와 TypeScript 타입 정의가 누락된 문제를 해결해야 합니다.
접근성과 타입 안정성을 위한 수정사항:
+interface MyUsersIconProps {
+ size?: number;
+ className?: string;
+ 'aria-label'?: string;
+ title?: string;
+}
+
-const MyUsersIcon = ({ size = 24, ...props }) => (
+const MyUsersIcon: React.FC<MyUsersIconProps> = ({
+ size = 24,
+ 'aria-label': ariaLabel = '사용자 아이콘',
+ title = '사용자 아이콘',
+ ...props
+}) => (
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 24 24'
+ aria-label={ariaLabel}
+ {...props}
>
+ <title>{title}</title>
<path
fill='#000'
d='m21.1 12.5 1.4 1.41-6.53 6.59L12.5 17l1.4-1.41 2.07 2.08zM11 4a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4m0 7c.68 0 1.5.09 2.41.26l-1.67 1.67-.74-.03c-2.97 0-6.1 1.46-6.1 2.1v1.1h6.2L13 20H3v-3c0-2.66 5.33-4 8-4'
/>
</svg>
);📝 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.
| const MyUsersIcon = ({ size = 24, ...props }) => ( | |
| <svg | |
| xmlns='http://www.w3.org/2000/svg' | |
| width={size} | |
| height={size} | |
| fill='none' | |
| viewBox='0 0 24 24' | |
| > | |
| <path | |
| fill='#000' | |
| d='m21.1 12.5 1.4 1.41-6.53 6.59L12.5 17l1.4-1.41 2.07 2.08zM11 4a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4m0 7c.68 0 1.5.09 2.41.26l-1.67 1.67-.74-.03c-2.97 0-6.1 1.46-6.1 2.1v1.1h6.2L13 20H3v-3c0-2.66 5.33-4 8-4' | |
| /> | |
| </svg> | |
| ); | |
| interface MyUsersIconProps { | |
| size?: number; | |
| className?: string; | |
| 'aria-label'?: string; | |
| title?: string; | |
| } | |
| const MyUsersIcon: React.FC<MyUsersIconProps> = ({ | |
| size = 24, | |
| 'aria-label': ariaLabel = '사용자 아이콘', | |
| title = '사용자 아이콘', | |
| ...props | |
| }) => ( | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width={size} | |
| height={size} | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| aria-label={ariaLabel} | |
| {...props} | |
| > | |
| <title>{title}</title> | |
| <path | |
| fill="#000" | |
| d="m21.1 12.5 1.4 1.41-6.53 6.59L12.5 17l1.4-1.41 2.07 2.08zM11 4a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4 2 2 0 0 0 0-4m0 7c.68 0 1.5.09 2.41.26l-1.67 1.67-.74-.03c-2.97 0-6.1 1.46-6.1 2.1v1.1h6.2L13 20H3v-3c0-2.66 5.33-4 8-4" | |
| /> | |
| </svg> | |
| ); |
🧰 Tools
🪛 Biome (2.1.2)
[error] 4-10: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
🤖 Prompt for AI Agents
In public/assets/svg/my-user.tsx around lines 3 to 16, the component lacks
TypeScript type definitions and accessibility attributes. Add explicit
TypeScript types for the component props, including size and any other props.
Also, include appropriate accessibility attributes such as role="img" and
aria-label or title to the SVG element to improve screen reader support.
| // 프로필 이미지 업로드 | ||
| export const useUploadProfileImage = () => { | ||
| const queryClient = useQueryClient(); | ||
| const { setUser, setLoading, setError } = useMyPageStore(); | ||
|
|
||
| const mutation = useMutation({ | ||
| mutationFn: async (file: File) => { | ||
| // 이미지 업로드 | ||
| const imageResponse = await uploadProfileImage(file); | ||
|
|
||
| // 사용자 정보 업데이트 (새 이미지 URL 포함) | ||
| const userResponse = await updateMyProfile({ | ||
| profileImageUrl: imageResponse.profileImageUrl, | ||
| }); | ||
|
|
||
| return { | ||
| imageResponse, | ||
| userResponse, | ||
| }; | ||
| }, | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| if (mutation.isPending) { | ||
| setLoading(true); | ||
| setError(null); | ||
| } | ||
|
|
||
| if (mutation.isSuccess && mutation.data) { | ||
| // 서버에서 업데이트된 사용자 정보로 캐시 업데이트 | ||
| const updatedUser = mutation.data.userResponse; | ||
|
|
||
| setUser(updatedUser); | ||
| queryClient.setQueryData(QUERY_KEYS.PROFILE, updatedUser); | ||
|
|
||
| setLoading(false); | ||
| alert('프로필 이미지가 성공적으로 업로드되었습니다!'); | ||
| } | ||
|
|
||
| if (mutation.isError) { | ||
| setError(mutation.error?.message || '이미지 업로드에 실패했습니다.'); | ||
| setLoading(false); | ||
| alert(`이미지 업로드 실패: ${mutation.error?.message}`); | ||
| } | ||
| }, [ | ||
| mutation.isPending, | ||
| mutation.isSuccess, | ||
| mutation.isError, | ||
| mutation.data, | ||
| mutation.error, | ||
| queryClient, | ||
| setUser, | ||
| setLoading, | ||
| setError, | ||
| ]); | ||
|
|
||
| return mutation; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
이미지 업로드 훅에서도 동일한 개선이 필요합니다
alert() 대체와 mutation 옵션 활용을 통해 더 나은 사용자 경험을 제공할 수 있습니다.
🤖 Prompt for AI Agents
In src/hooks/useMyPageQueries.ts lines 93 to 150, replace the alert() calls in
the useEffect with mutation options such as onSuccess and onError callbacks to
handle success and error notifications more cleanly. Remove alert() usage and
instead use these callbacks to update loading, error states, and show
user-friendly messages, improving UX and code clarity.
| const lastPathChangeRef = useRef<number>(0); | ||
|
|
||
| // 디바운싱 | ||
| const debouncedDeviceType = useDebounce(deviceType, 700); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
디바운스 지연 시간을 줄이는 것을 고려해보세요.
700ms의 디바운스 지연 시간은 사용자 경험 관점에서 다소 길 수 있습니다. 화면 크기 변경 시 더 빠른 반응성을 위해 300-500ms 정도로 줄이는 것을 고려해보세요.
- const debouncedDeviceType = useDebounce(deviceType, 700);
+ const debouncedDeviceType = useDebounce(deviceType, 300);📝 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.
| const debouncedDeviceType = useDebounce(deviceType, 700); | |
| const debouncedDeviceType = useDebounce(deviceType, 300); |
🤖 Prompt for AI Agents
In src/hooks/useResponsiveRouting.ts at line 23, the debounce delay for
deviceType is set to 700ms, which may cause slower responsiveness on screen size
changes. Reduce the debounce delay to a value between 300 and 500 milliseconds
to improve user experience by making the routing react faster to device type
changes.
| // 사용자가 최근에 클릭했다면 (3초 이내) 자동 리다이렉트 무시 | ||
| if (timeSinceLastPathChange < 3000) { | ||
| lastDeviceTypeRef.current = debouncedDeviceType; | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
사용자 네비게이션 무시 시간을 조정해보세요.
3초의 무시 시간이 너무 길 수 있습니다. 사용자가 의도적으로 화면을 회전시키거나 크기를 변경할 때 반응성이 떨어질 수 있습니다. 1-2초 정도로 줄이는 것을 고려해보세요.
- // 사용자가 최근에 클릭했다면 (3초 이내) 자동 리다이렉트 무시
- if (timeSinceLastPathChange < 3000) {
+ // 사용자가 최근에 클릭했다면 (1.5초 이내) 자동 리다이렉트 무시
+ if (timeSinceLastPathChange < 1500) {📝 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.
| // 사용자가 최근에 클릭했다면 (3초 이내) 자동 리다이렉트 무시 | |
| if (timeSinceLastPathChange < 3000) { | |
| lastDeviceTypeRef.current = debouncedDeviceType; | |
| return; | |
| } | |
| // 사용자가 최근에 클릭했다면 (1.5초 이내) 자동 리다이렉트 무시 | |
| if (timeSinceLastPathChange < 1500) { | |
| lastDeviceTypeRef.current = debouncedDeviceType; | |
| return; | |
| } |
🤖 Prompt for AI Agents
In src/hooks/useResponsiveRouting.ts around lines 44 to 48, the current user
navigation ignore time is set to 3 seconds, which may reduce responsiveness when
users intentionally change screen orientation or size. Adjust the
timeSinceLastPathChange threshold from 3000 milliseconds to a shorter duration,
such as 1000 or 2000 milliseconds, to improve responsiveness while still
preventing unwanted automatic redirects.
src/stores/MyPage/useMyPageStore.ts
Outdated
| const useMyPageStore = create<MyPageStoreState>((set, get) => ({ | ||
| user: null, | ||
| isLoading: false, | ||
| error: null, | ||
| isEditing: false, | ||
|
|
||
| // 사용자 관련 액션 | ||
| setUser: (user) => { | ||
| set({ | ||
| user, | ||
| error: null, | ||
| }); | ||
| }, | ||
|
|
||
| // 로딩 상태 액션 | ||
| setLoading: (isLoading) => { | ||
| set({ isLoading }); | ||
| }, | ||
|
|
||
| // 에러 관리 액션 | ||
| setError: (error) => { | ||
| set({ | ||
| error, | ||
| isLoading: false, | ||
| }); | ||
| }, | ||
|
|
||
| clearError: () => { | ||
| set({ error: null }); | ||
| }, | ||
|
|
||
| // UI 상태 액션 | ||
| setEditing: (isEditing) => { | ||
| set({ isEditing }); | ||
| }, | ||
| })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Zustand 스토어가 깔끔하게 구현되었습니다.
상태 관리 로직이 명확하게 구조화되어 있고, 액션들이 기능별로 잘 분류되어 있습니다. 에러 처리 시 로딩 상태를 자동으로 해제하는 것도 좋은 구현입니다.
개발자 경험과 견고성을 위한 선택적 개선사항:
const useMyPageStore = create<MyPageStoreState>((set, get) => ({
user: null,
isLoading: false,
error: null,
isEditing: false,
// 사용자 관련 액션
setUser: (user) => {
+ // 개발 환경에서 상태 변경 로깅
+ if (process.env.NODE_ENV === 'development') {
+ console.log('MyPage: User updated', user);
+ }
set({
user,
error: null,
});
},
// 에러 관리 액션
setError: (error) => {
+ // 개발 환경에서 에러 로깅
+ if (process.env.NODE_ENV === 'development') {
+ console.error('MyPage: Error occurred', error);
+ }
set({
error,
isLoading: false,
});
},
+ // 편의를 위한 셀렉터 추가
+ isUserLoaded: () => {
+ const { user, isLoading } = get();
+ return user !== null && !isLoading;
+ },
+
+ // 상태 초기화 액션
+ reset: () => {
+ set({
+ user: null,
+ isLoading: false,
+ error: null,
+ isEditing: false,
+ });
+ },
}));이러한 개선사항들은 디버깅과 상태 관리를 더욱 편리하게 만들어줍니다.
📝 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.
| const useMyPageStore = create<MyPageStoreState>((set, get) => ({ | |
| user: null, | |
| isLoading: false, | |
| error: null, | |
| isEditing: false, | |
| // 사용자 관련 액션 | |
| setUser: (user) => { | |
| set({ | |
| user, | |
| error: null, | |
| }); | |
| }, | |
| // 로딩 상태 액션 | |
| setLoading: (isLoading) => { | |
| set({ isLoading }); | |
| }, | |
| // 에러 관리 액션 | |
| setError: (error) => { | |
| set({ | |
| error, | |
| isLoading: false, | |
| }); | |
| }, | |
| clearError: () => { | |
| set({ error: null }); | |
| }, | |
| // UI 상태 액션 | |
| setEditing: (isEditing) => { | |
| set({ isEditing }); | |
| }, | |
| })); | |
| const useMyPageStore = create<MyPageStoreState>((set, get) => ({ | |
| user: null, | |
| isLoading: false, | |
| error: null, | |
| isEditing: false, | |
| // 사용자 관련 액션 | |
| setUser: (user) => { | |
| // 개발 환경에서 상태 변경 로깅 | |
| if (process.env.NODE_ENV === 'development') { | |
| console.log('MyPage: User updated', user); | |
| } | |
| set({ | |
| user, | |
| error: null, | |
| }); | |
| }, | |
| // 로딩 상태 액션 | |
| setLoading: (isLoading) => { | |
| set({ isLoading }); | |
| }, | |
| // 에러 관리 액션 | |
| setError: (error) => { | |
| // 개발 환경에서 에러 로깅 | |
| if (process.env.NODE_ENV === 'development') { | |
| console.error('MyPage: Error occurred', error); | |
| } | |
| set({ | |
| error, | |
| isLoading: false, | |
| }); | |
| }, | |
| clearError: () => { | |
| set({ error: null }); | |
| }, | |
| // UI 상태 액션 | |
| setEditing: (isEditing) => { | |
| set({ isEditing }); | |
| }, | |
| // 편의를 위한 셀렉터 추가 | |
| isUserLoaded: () => { | |
| const { user, isLoading } = get(); | |
| return user !== null && !isLoading; | |
| }, | |
| // 상태 초기화 액션 | |
| reset: () => { | |
| set({ | |
| user: null, | |
| isLoading: false, | |
| error: null, | |
| isEditing: false, | |
| }); | |
| }, | |
| })); |
🤖 Prompt for AI Agents
In src/stores/MyPage/useMyPageStore.ts between lines 10 and 45, the Zustand
store implementation is clean and well-structured. To further improve developer
experience and robustness, consider adding optional enhancements such as logging
state changes for easier debugging, or implementing middleware for persistence
or devtools integration. These additions will help track state updates and
errors more effectively without altering the existing logic.
| // 마이페이지 스토어 상태 타입 | ||
| export interface MyPageStoreState { | ||
| // 사용자 정보 | ||
| user: User | null; | ||
| isLoading: boolean; | ||
| error: string | null; | ||
|
|
||
| // UI 상태 | ||
| isEditing: boolean; | ||
|
|
||
| // 액션들 | ||
| setUser: (user: User | null) => void; | ||
| setLoading: (loading: boolean) => void; | ||
| setError: (error: string | null) => void; | ||
| setEditing: (editing: boolean) => void; | ||
| clearError: () => void; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
clearError 메서드 중복 제거 고려
clearError() 메서드는 setError(null)과 동일한 기능을 수행합니다. 코드 간소화를 위해 제거를 고려해보세요.
- clearError: () => void;clearError() 호출 대신 setError(null)을 직접 사용하면 됩니다.
📝 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.
| // 마이페이지 스토어 상태 타입 | |
| export interface MyPageStoreState { | |
| // 사용자 정보 | |
| user: User | null; | |
| isLoading: boolean; | |
| error: string | null; | |
| // UI 상태 | |
| isEditing: boolean; | |
| // 액션들 | |
| setUser: (user: User | null) => void; | |
| setLoading: (loading: boolean) => void; | |
| setError: (error: string | null) => void; | |
| setEditing: (editing: boolean) => void; | |
| clearError: () => void; | |
| } | |
| // 마이페이지 스토어 상태 타입 | |
| export interface MyPageStoreState { | |
| // 사용자 정보 | |
| user: User | null; | |
| isLoading: boolean; | |
| error: string | null; | |
| // UI 상태 | |
| isEditing: boolean; | |
| // 액션들 | |
| setUser: (user: User | null) => void; | |
| setLoading: (loading: boolean) => void; | |
| setError: (error: string | null) => void; | |
| setEditing: (editing: boolean) => void; | |
| } |
🤖 Prompt for AI Agents
In src/types/mypageTypes.ts between lines 13 and 29, the clearError method
duplicates the functionality of setError(null). To simplify the code, remove the
clearError method from the interface and replace all clearError() calls in the
codebase with setError(null).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (6)
public/assets/svg/my-activities.tsx (1)
4-16: 접근성을 위한 aria-label 또는 title 요소 추가가 필요합니다.이전 리뷰에서 이미 지적된 것과 동일한 접근성 문제입니다. SVG 아이콘에 스크린 리더를 위한 대체 텍스트가 필요합니다.
public/assets/svg/my-activities-dashboard.tsx (1)
4-16: 접근성을 위한 aria-label 또는 title 요소 추가가 필요합니다.이전 리뷰에서 이미 지적된 것과 동일한 접근성 문제입니다. 스크린 리더 사용자를 위해 적절한 설명을 추가해주세요.
public/assets/svg/my-reservation.tsx (1)
4-16: 접근성을 위한 aria-label 또는 title 요소 추가가 필요합니다.이전 리뷰에서 이미 지적된 것과 동일한 접근성 문제입니다. SVG 아이콘에 스크린 리더를 위한 대체 텍스트가 필요합니다.
public/assets/svg/my-user.tsx (1)
3-17: 접근성 문제와 TypeScript 타입 정의 개선이 필요합니다.이전 리뷰에서 이미 지적된 것과 동일한 문제들입니다:
- SVG 접근성을 위한 aria-label 또는 title 요소 누락
- TypeScript 타입 정의 누락
public/assets/svg/pen.tsx (1)
4-11: 접근성 메타데이터 누락 –<title>또는aria-label추가 필요이 SVG는 여전히 대체 텍스트가 없어 스크린 리더가 아이콘 의미를 파악할 수 없습니다. 이전 리뷰에서도 동일한 지적이 있었으므로 중복 이슈로 분류합니다. 아래와 같이 간단히 보완해 주세요.
<svg xmlns='http://www.w3.org/2000/svg' width={size} height={size} fill='none' viewBox='0 0 25 24' + role='img' + aria-label='펜 아이콘' {...props} > + <title>Pen icon</title>public/assets/svg/profile-default.tsx (1)
4-11: 접근성 속성 누락으로 스크린리더 지원이 불가능합니다지난 리뷰에서 이미 지적된 사항(
role="img"및<title>또는aria-label추가 )이 그대로 남아 있습니다. 최소한 다음과 같이 보강해 주세요.<svg xmlns='http://www.w3.org/2000/svg' width={size} height={size} fill='none' viewBox='0 0 160 160' + role='img' + aria-label='default profile icon' {...props} > + <title>Default user avatar</title>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (6)
public/assets/svg/my-activities-dashboard.tsx(1 hunks)public/assets/svg/my-activities.tsx(1 hunks)public/assets/svg/my-reservation.tsx(1 hunks)public/assets/svg/my-user.tsx(1 hunks)public/assets/svg/pen.tsx(1 hunks)public/assets/svg/profile-default.tsx(1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
public/assets/svg/my-activities-dashboard.tsx
[error] 4-11: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/my-reservation.tsx
[error] 4-11: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/profile-default.tsx
[error] 4-11: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/my-activities.tsx
[error] 4-11: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/pen.tsx
[error] 4-11: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
public/assets/svg/my-user.tsx
[error] 4-11: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
🔇 Additional comments (1)
public/assets/svg/pen.tsx (1)
6-9:viewBox비율 불일치
width·height기본값은 24×24인데viewBox는 25×24라서 비율이 살짝 틀어집니다. 디자인 의도가 아니라면viewBox='0 0 24 24'로 통일하는 편이 안전합니다.
| @@ -0,0 +1,22 @@ | |||
| import React from 'react'; | |||
|
|
|||
| const PenIcon = ({ size = 24, ...props }) => ( | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Props 타입 선언 누락
파일 확장자가 .tsx인 만큼 TypeScript 타입을 명시하면 컴파일 단계에서 오타·누락을 예방할 수 있습니다.
-const PenIcon = ({ size = 24, ...props }) => (
+interface PenIconProps extends React.SVGProps<SVGSVGElement> {
+ size?: number;
+}
+
+const PenIcon: React.FC<PenIconProps> = ({ size = 24, ...props }) => (📝 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.
| const PenIcon = ({ size = 24, ...props }) => ( | |
| interface PenIconProps extends React.SVGProps<SVGSVGElement> { | |
| size?: number; | |
| } | |
| const PenIcon: React.FC<PenIconProps> = ({ size = 24, ...props }) => ( |
🤖 Prompt for AI Agents
In public/assets/svg/pen.tsx at line 3, the PenIcon component lacks explicit
TypeScript type declarations for its props. Define a proper interface or type
for the props, including the size property and any other props, and annotate the
PenIcon function parameter with this type to ensure type safety and prevent
typos or omissions during compilation.
| @@ -0,0 +1,19 @@ | |||
| import React from 'react'; | |||
|
|
|||
| const ProfileDefaultIcon = ({ size = 24, ...props }) => ( | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Props 타입 선언이 빠져 있습니다
TSX 파일인데 props가 암시적 any 입니다. SVGProps<SVGSVGElement>를 활용해 명시적으로 선언하면 IDE 지원과 타입 안전성이 향상됩니다.
-import React from 'react';
-
-const ProfileDefaultIcon = ({ size = 24, ...props }) => (
+import React, { type SVGProps } from 'react';
+
+interface ProfileDefaultIconProps extends SVGProps<SVGSVGElement> {
+ size?: number;
+}
+
+const ProfileDefaultIcon = ({ size = 24, ...props }: ProfileDefaultIconProps) => (📝 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.
| const ProfileDefaultIcon = ({ size = 24, ...props }) => ( | |
| import React, { type SVGProps } from 'react'; | |
| interface ProfileDefaultIconProps extends SVGProps<SVGSVGElement> { | |
| size?: number; | |
| } | |
| const ProfileDefaultIcon = ({ size = 24, ...props }: ProfileDefaultIconProps) => ( |
🤖 Prompt for AI Agents
In public/assets/svg/profile-default.tsx at line 3, the props parameter lacks an
explicit type declaration, causing it to be implicitly any. To fix this, import
SVGProps from React and declare the component props as ({ size = 24, ...props }:
SVGProps<SVGSVGElement>) to ensure proper type checking and IDE support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/app/(with-header)/mypage/page.tsx (1)
87-101: 프로필 이미지 업로드 기능이 올바르게 구현되었습니다.과거 리뷰 코멘트에서 요청된 alert 플레이스홀더를 실제 이미지 업로드 기능으로 교체하는 작업이 완료되었습니다.
useProfileImageUpload훅을 사용하여 적절히 구현되어 있습니다.src/app/(with-header)/mypage/components/ProfileNavigation.tsx (1)
22-23: 프로필 이미지 업로드 로직의 에러 처리 개선이 필요합니다.
useProfileImageUpload훅에서 여전히alert()를 사용하고 있어 사용자 경험을 해칠 수 있습니다. 과거 리뷰 코멘트에서도 언급되었던 이슈입니다.토스트 알림이나 인라인 오류 메시지 사용을 권장합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
src/app/(with-header)/mypage/components/ProfileNavigation.tsx(1 hunks)src/app/(with-header)/mypage/page.tsx(1 hunks)src/hooks/useProfileImageUpload.ts(1 hunks)
🔇 Additional comments (6)
src/hooks/useProfileImageUpload.ts (1)
32-36: 파일 크기 검증 로직이 적절합니다.5MB 파일 크기 제한은 리소스 효율성과 보안을 위한 적절한 설정입니다. PR 목적에서 언급된 AI 코드 리뷰 권장사항에 따른 구현이 잘 되어 있습니다.
src/app/(with-header)/mypage/page.tsx (2)
32-36: 반응형 라우팅 로직이 적절하게 구현되었습니다.
mounted상태와deviceType을 체크하여 데스크톱/태블릿에서는/mypage/profile로 자동 리다이렉트하는 로직이 잘 구현되어 있습니다. 이는 PR 목적에서 언급된 디바이스별 라우팅 요구사항을 충족합니다.
58-78: 스켈레톤 UI가 적절하게 구현되었습니다.로딩 상태나 리다이렉트 중에 표시되는 스켈레톤 UI가 실제 콘텐츠 구조와 일치하며, 애니메이션 효과도 적절히 적용되어 있습니다.
src/app/(with-header)/mypage/components/ProfileNavigation.tsx (3)
45-47: 메뉴 활성화 상태 확인 로직이 적절합니다.
pathname을 사용하여 현재 경로와 메뉴 항목의 경로를 비교하는 로직이 간단하고 효율적입니다.
50-51: 반응형 디자인이 적절하게 구현되었습니다.
hidden md:block을 사용하여 모바일에서는 숨기고 중간 이상 화면에서만 표시하는 반응형 로직이 적절합니다. 또한lg:w-384를 사용한 반응형 너비 조정도 좋습니다.
77-81: 메뉴 항목 스타일링이 적절하게 구현되었습니다.활성화된 메뉴는
bg-green-200 text-green-300으로, 비활성화된 메뉴는 호버 시hover:bg-gray-100으로 적절한 시각적 피드백을 제공합니다. 사용자가 현재 위치를 쉽게 파악할 수 있습니다.
| if (!file.type.startsWith('image/')) { | ||
| alert('이미지 파일만 업로드 가능합니다.'); | ||
| return; | ||
| } | ||
|
|
||
| // 파일 크기 검증 | ||
| if (file.size > 5 * 1024 * 1024) { | ||
| alert('파일 크기는 5MB 이하여야 합니다.'); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
사용자 경험 개선을 위해 alert 대신 토스트 알림 사용을 고려하세요.
현재 alert()를 사용한 오류 메시지는 브라우저를 차단하고 구식 UX를 제공합니다.
토스트 라이브러리나 인라인 오류 메시지 사용을 권장합니다:
// 예시: 토스트 라이브러리 사용
import { toast } from 'react-hot-toast';
// 파일 타입 검증
if (!file.type.startsWith('image/')) {
- alert('이미지 파일만 업로드 가능합니다.');
+ toast.error('이미지 파일만 업로드 가능합니다.');
return;
}
// 파일 크기 검증
if (file.size > 5 * 1024 * 1024) {
- alert('파일 크기는 5MB 이하여야 합니다.');
+ toast.error('파일 크기는 5MB 이하여야 합니다.');
return;
}📝 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.
| if (!file.type.startsWith('image/')) { | |
| alert('이미지 파일만 업로드 가능합니다.'); | |
| return; | |
| } | |
| // 파일 크기 검증 | |
| if (file.size > 5 * 1024 * 1024) { | |
| alert('파일 크기는 5MB 이하여야 합니다.'); | |
| return; | |
| } | |
| import { toast } from 'react-hot-toast'; | |
| if (!file.type.startsWith('image/')) { | |
| toast.error('이미지 파일만 업로드 가능합니다.'); | |
| return; | |
| } | |
| // 파일 크기 검증 | |
| if (file.size > 5 * 1024 * 1024) { | |
| toast.error('파일 크기는 5MB 이하여야 합니다.'); | |
| return; | |
| } |
🤖 Prompt for AI Agents
In src/hooks/useProfileImageUpload.ts around lines 27 to 36, replace the
blocking alert() calls used for file type and size validation with non-blocking
toast notifications or inline error messages using a toast library or UI
component. This will improve user experience by providing modern, non-intrusive
feedback without interrupting the user's workflow.
| uploadProfileImage(file); | ||
| } | ||
|
|
||
| // 같은 파일을 다시 선택할 수 있도록 input 값 초기화 | ||
| event.target.value = ''; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
업로드 에러 처리 개선을 고려하세요.
현재 uploadProfileImage 호출 시 에러 처리가 없습니다. 네트워크 오류나 서버 오류 시 사용자에게 피드백을 제공해야 합니다.
다음과 같은 개선을 제안합니다:
// useUploadProfileImage 훅에서 onError 콜백 활용
const { mutate: uploadProfileImage } = useUploadProfileImage({
onError: (error) => {
toast.error('이미지 업로드에 실패했습니다. 다시 시도해주세요.');
},
onSuccess: () => {
toast.success('프로필 이미지가 업데이트되었습니다.');
}
});🤖 Prompt for AI Agents
In src/hooks/useProfileImageUpload.ts around lines 38 to 43, the call to
uploadProfileImage lacks error handling, so users receive no feedback on upload
failures. Modify the useUploadProfileImage hook usage to include onError and
onSuccess callbacks that show toast notifications for failure and success
respectively, providing clear user feedback on the upload status.
evaain706
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨습니다! 전체적으로 깔끔하게 useQuery, useMutation과 같은 tanstack query기능과
route.ts를 경유하여 요청을 보내도록 잘 구현해주신것같습니다!
코드분리도 엄청 잘하신것같아요! 배워가겠습니다!
minimo-9
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생많으셨습니다! 중첩을 이용해 구현해주신 부분 좋았습니다!
📌 변경 사항 개요
MyPage를 구현 완료했습니다. 반응형 UI + API 연동까지 완료했습니다.
📝 상세 내용
동적 라우팅
/mypage(프로필 이미지 및 메뉴 네비게이션) -> 내 정보 메뉴 클릭 후/mypage/profile/mypage/profile(프로필 이미지 및 메뉴 네비게이션+내 정보 인풋폼)API 연동
🔗 관련 이슈
🖼️ 스크린샷(선택사항)
ui.mov
default.mov
default.mov
💡 참고 사항
Summary by CodeRabbit
신규 기능
버그 수정
문서화