Skip to content

Conversation

@LeeCh0129
Copy link
Contributor

@LeeCh0129 LeeCh0129 commented Jul 26, 2025

📌 변경 사항 개요

MyPage를 구현 완료했습니다. 반응형 UI + API 연동까지 완료했습니다.

📝 상세 내용

동적 라우팅

  • 모바일: /mypage(프로필 이미지 및 메뉴 네비게이션) -> 내 정보 메뉴 클릭 후 /mypage/profile
  • 데스크톱/태블릿: /mypage/profile (프로필 이미지 및 메뉴 네비게이션+내 정보 인풋폼)
  • 화면 크기 변경 시 자동 URL 전환 - 깜빡거리는 현상과 무한 리다이렉트 문제가 발생하여 디바운싱 적용

API 연동

  • 로그인/회원가입에서 사용하던 유효성 검사 적용(비밀번호 8자 이상, 확인 일치)
  • 이미지 업로드는 AI 코드 리뷰를 통해서 용량 제한을 두는것이 리소스 절약 및 보안적인 이유로 제한을 두는 것이 좋다는 리뷰를 받아 5MB로 제한

🔗 관련 이슈

🖼️ 스크린샷(선택사항)

ui.mov
default.mov
default.mov

💡 참고 사항

  • 환경변수 머지 직전 개발서버에 등록예정

Summary by CodeRabbit

  • 신규 기능

    • 마이페이지(내 정보) 관련 페이지 및 컴포넌트가 추가되었습니다: 프로필, 예약 내역, 활동 관리, 대시보드 등.
    • 프로필 이미지 업로드 및 편집 기능이 지원됩니다.
    • 데스크톱/태블릿/모바일 환경에 따라 마이페이지 레이아웃과 라우팅이 자동으로 전환됩니다.
    • 프로필 정보(닉네임, 비밀번호 등) 수정이 가능합니다.
    • 각종 마이페이지 메뉴에 아이콘이 적용되었습니다.
    • 사용자 프로필 이미지 표시 및 편집 UI가 개선되었습니다.
    • 프로필 이미지 업로드 및 프로필 정보 API 연동 기능이 추가되었습니다.
    • 마이페이지 내비게이션과 레이아웃 컴포넌트가 도입되어 사용자 경험이 향상되었습니다.
    • 사용자 프로필 데이터 관리 및 상태 동기화 기능이 추가되었습니다.
    • 입력값 디바운스 처리 및 반응형 라우팅 훅이 도입되었습니다.
  • 버그 수정

    • 외부 이미지 도메인 허용 설정이 추가되어 이미지 최적화 및 표시가 개선되었습니다.
  • 문서화

    • 마이페이지 관련 타입 및 인터페이스가 추가되어 사용성 및 유지보수가 향상되었습니다.

@LeeCh0129 LeeCh0129 self-assigned this Jul 26, 2025
@LeeCh0129 LeeCh0129 added the ✨ Feature 기능구현 label Jul 26, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 26, 2025

"""

Walkthrough

이 변경사항은 "내 정보" 마이페이지 기능의 전체적인 구현을 포함합니다. 사용자 프로필 정보 조회, 수정, 이미지 업로드, 내비게이션, 반응형 라우팅, 상태 관리, 관련 API 라우트 및 타입 정의, SVG 아이콘 컴포넌트 추가 등이 새롭게 도입되었습니다. 모든 주요 마이페이지 하위 페이지와 컴포넌트가 추가되었습니다.

Changes

파일/경로 요약 변경 요약
next.config.ts 이미지 최적화를 위해 S3 버킷 remotePatterns 추가
public/assets/svg/my-activities-dashboard.tsx
public/assets/svg/my-activities.tsx
public/assets/svg/my-reservation.tsx
public/assets/svg/my-user.tsx
public/assets/svg/pen.tsx
public/assets/svg/profile-default.tsx
마이페이지용 SVG 아이콘 컴포넌트 6종 신규 추가
src/apis/mypage.ts 프로필 조회, 수정, 이미지 업로드 API 함수 3종 신규 추가
src/app/(with-header)/mypage/activities/page.tsx
src/app/(with-header)/mypage/dashboard/page.tsx
src/app/(with-header)/mypage/profile/page.tsx
src/app/(with-header)/mypage/reservations/page.tsx
src/app/(with-header)/mypage/page.tsx
마이페이지 하위 페이지(내 체험, 대시보드, 프로필, 예약 내역, 메인) 컴포넌트 신규 추가
src/app/(with-header)/mypage/components/ProfileImage.tsx
src/app/(with-header)/mypage/components/ProfileNavigation.tsx
프로필 이미지, 프로필 내비게이션 컴포넌트 신규 추가
src/app/(with-header)/mypage/components/index.ts ProfileImage, ProfileNavigation 컴포넌트 재출력용 인덱스 파일 신규 추가
src/app/(with-header)/mypage/layout.tsx 마이페이지 레이아웃 컴포넌트 신규 추가 (로딩/에러/정상 상태 분기)
src/app/api/users/me/route.ts
src/app/api/users/me/image/route.ts
사용자 프로필 조회/수정, 프로필 이미지 업로드 API 라우트 신규 추가
src/hooks/useDebounce.ts useDebounce 커스텀 훅 신규 추가
src/hooks/useMyPageQueries.ts 프로필 조회/수정/이미지 업로드 React Query 훅 및 상태 동기화 신규 추가
src/hooks/useResponsiveRouting.ts 반응형 라우팅 커스텀 훅 신규 추가
src/stores/MyPage/useMyPageStore.ts Zustand 기반 마이페이지 상태 관리 스토어 신규 추가
src/types/mypageTypes.ts 프로필 이미지, 상태, 요청/응답, 폼 데이터 등 관련 타입 정의 신규 추가
src/hooks/useProfileImageUpload.ts 프로필 이미지 업로드 처리 커스텀 훅 신규 추가

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: 최신 프로필 이미지 반영
Loading
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: 결과 반영 및 상태 업데이트
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40분

Suggested reviewers

  • minimo-9
  • evaain706

Poem

🐰
마이페이지에 봄이 왔어요,
프로필 사진, 닉네임, 내비게이션도 새로워요!
이미지 업로드, 정보 수정,
반응형 라우팅까지 척척!
토끼도 놀란 깔끔한 변화,
리뷰어님, 이 변화와 함께
오늘도 힘내세요!

"""

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/61

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee9197a and 7ef79e7.

📒 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 설정도 합리적입니다.

Comment on lines 4 to 10
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 24 24'
>
Copy link

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.

Suggested change
<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.

Comment on lines 3 to 16
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>
);
Copy link

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.

Suggested change
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.

Comment on lines 3 to 16
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>
);
Copy link

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.

Suggested change
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.

Comment on lines 3 to 16
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>
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

접근성 문제와 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.

Suggested change
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.

Comment on lines +93 to +150
// 프로필 이미지 업로드
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;
};
Copy link

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);
Copy link

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.

Suggested change
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.

Comment on lines +44 to +48
// 사용자가 최근에 클릭했다면 (3초 이내) 자동 리다이렉트 무시
if (timeSinceLastPathChange < 3000) {
lastDeviceTypeRef.current = debouncedDeviceType;
return;
}
Copy link

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.

Suggested change
// 사용자가 최근에 클릭했다면 (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.

Comment on lines 10 to 45
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 });
},
}));
Copy link

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.

Suggested change
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.

Comment on lines +13 to +29
// 마이페이지 스토어 상태 타입
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;
}
Copy link

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.

Suggested change
// 마이페이지 스토어 상태 타입
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).

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

📥 Commits

Reviewing files that changed from the base of the PR and between 00e2d81 and 47b662f.

📒 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 }) => (
Copy link

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.

Suggested change
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 }) => (
Copy link

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.

Suggested change
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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 47b662f and 89cc384.

📒 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으로 적절한 시각적 피드백을 제공합니다. 사용자가 현재 위치를 쉽게 파악할 수 있습니다.

Comment on lines +27 to +36
if (!file.type.startsWith('image/')) {
alert('이미지 파일만 업로드 가능합니다.');
return;
}

// 파일 크기 검증
if (file.size > 5 * 1024 * 1024) {
alert('파일 크기는 5MB 이하여야 합니다.');
return;
}
Copy link

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.

Suggested change
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.

Comment on lines +38 to +43
uploadProfileImage(file);
}

// 같은 파일을 다시 선택할 수 있도록 input 값 초기화
event.target.value = '';
};
Copy link

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.

Copy link
Contributor

@evaain706 evaain706 left a 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를 경유하여 요청을 보내도록 잘 구현해주신것같습니다!
코드분리도 엄청 잘하신것같아요! 배워가겠습니다!

Copy link
Contributor

@minimo-9 minimo-9 left a comment

Choose a reason for hiding this comment

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

고생많으셨습니다! 중첩을 이용해 구현해주신 부분 좋았습니다!

@LeeCh0129 LeeCh0129 merged commit c0f7c99 into develop Jul 28, 2025
2 checks passed
@LeeCh0129 LeeCh0129 deleted the feat/61 branch July 28, 2025 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 내 정보 페이지 구현

4 participants