Skip to content

Conversation

@yuj2n
Copy link
Contributor

@yuj2n yuj2n commented Jun 19, 2025

📌 변경 사항 개요

헤더 일부 API 연동(공동 사용자 프로필 제외)
대시보드 수정 페이지 일부 API 연동(구성원 삭제 및 이메일 초대내역 API 제외)

✨ 요약

  • 대시보드 수정 페이지 API 작업 중 헤더 API 작업과 같이 되어야할 부분이 많아 보여 헤더 API 작업 중 많은 부분이 같이 작업하게 되어 헤더 API 브랜치에 대시보드 수정 페이지 작업까지 한꺼번에 많은 작업이 이루어지게 되었습니다 ㅎㅎ...

📝 상세 내용

헤더

  • 대시보드 명의 경우 사이드 바를 클릭하면 해당 대시보드 명을 지닌 헤더로 이동하여야 하므로 사이드 바에서 가져오는 대시보드 데이터를 반영하도록 작성하였습니다,
  • 관리 버튼의 경우 /dashboard/${selectedDashboard?.id}/edit 페이지로 이동하도록 구현하였고 여기서 selectedDashboard는 사이드 바에서 가져오는 대시보드 데이터를 useSelectedDashboardStore.ts 파일에서 zustand를 이용하여 전역으로 관리하도록 하여 같은 정보를 보여주는 사이드 바, 헤더, 수정 폼의 대시보드 제목이 상태 공유를 보다 편리하게 할 수 있도록 작성하였습니다.
  • 초대하기 버튼의 경우 초대하기 모달을 연동하여 이메일로 초대가 간 것을 Swagger UI를 통해 확인하였습니다.
  • 공동 사용자 프로필의 경우 아직 많은 사용자 정보를 저장하고 확인해보지 않아 API 연동을 하지 못하였고 바로 다음 작업으로 빠르게 진행해보도록 하겠습니다!
  • 본인 사용자 프로필의 경우 이미지를 추가한 경우 디자인이 어색하지 않게 보이는 것을 Swagger UI를 통해 확인하였습니다.
  • 드롭다운 메뉴의 마이페이지와 로그아웃 버튼 로직 연동하여 적용된 것 확인하였습니다

대시보드 수정 페이지

  • 대시보드 정보 수정 컴포넌트의 경우 변경 버튼 클릭 시 사이드 바와 헤더, 수정 정보 제목에도 반영이 되어야 하는 점으로 인해 사이드 바에서 가져오는 대시보드 데이터를 전역으로 관리하여 버튼 클릭 시 전역 상태가 동기화되도록 코드 수정하였습니다.(찬호님이 작성하신 Sidebar.tsx/useDashboard.ts 파일의 경우 일부 수정되었습니다)
  • 구성원 삭제 컴포넌트의 경우 페이지네이션 적용하여 작동 확인하였고 버튼이 이미지로 구현하여 위치가 어긋나 보이는 문제가 있는데 (특히 다크모드에서) 회의 후 너무 부자연스러워 보이는 경우 버튼을 직접 커스텀하거나 해결방안을 생각해보도록 하겠습니다. 공동 사용자 프로필이 적용이 안되었기 때문에 이 부분도 아직 반영하여 데이터를 불러오지 못하여 다음 작업 때 반영하도록 하겠습니다.
  • 초대 내역 컴포넌트의 경우 페이지네이션 구현과 초대하기 버튼 클릭 시 이메일 초대하기 모달까지의 구현은 완성이 되었으며 초대한 이메일 내역을 불러오는 API 연동은 아직 되어있지 않아 다음 작업으로 진행할 예정입니다.
  • 대시보드 삭제하기 버튼의 경우 클릭 시 대시보드 삭제되는 것 Swagger UI를 통하여 적용 잘 되는 것 확인하였습니다.

🔗 관련 이슈

🖼️ 스크린샷

20250619_210223.mp4

✅ 체크리스트

  • 브랜치 네이밍 컨벤션을 준수했습니다
  • 커밋 컨벤션을 준수했습니다
  • 코드가 프로젝트의 스타일 가이드라인을 준수합니다

💡 참고 사항

  • 사이드 바에서 대시보드를 클릭하여 접근하는 것이 아닌 바로 URL에 http://localhost:3000/dashboard/15209/edit을 쳐서 접속하는 경우에 헤더의 대시보드 명이 보이지 않는 문제가 있음 -> 바로 접근 못하도록 해야할 것 같음
  • 로그아웃 후 이전 버튼을 누르면 사용자 정보가 없는 아래와 같은 페이지가 보이게 됩니다. (이동을 막는 로직이 필요해 보임)
    image
  • 아직 /dashboard -> /dashboard/${id} 흐름의 작업이 완성되어 있지 않아 /dashboard/${id}/edit로의 이동이 잘 되는지 확인하지 못하였고 따라서 해당 url의 id 값에 따라 대시보드 삭제하기 버튼 확인을 위해서는 직접 해당 대시보드의 id를 입력 후 삭제 버튼을 눌러야 확인할 수 있습니다.

Summary by CodeRabbit

  • 새로운 기능

    • 대시보드 편집 페이지에 삭제 기능과 뒤로가기 버튼이 추가되고, 삭제 버튼 UI가 개선되었습니다.
    • 대시보드 초대 기능에 이메일 초대 모달과 초대 목록 페이지네이션이 도입되었습니다.
    • 대시보드 멤버 목록에 페이지네이션이 적용되었습니다.
    • 글로벌 모달 렌더러와 모달 상태 관리가 통합되어 다양한 모달을 일괄 관리합니다.
    • 헤더가 모듈화되어 대시보드 정보와 네비게이션이 개선되었습니다.
    • 사용자 아바타와 사용자 정보 컴포넌트가 인증 상태 기반으로 변경되었습니다.
    • 대시보드 선택 상태를 전역으로 관리하는 스토어가 추가되었습니다.
    • 대시보드 데이터 조회가 React Query를 활용하는 방식으로 개선되었습니다.
    • 신규 초대 API 기능이 추가되었습니다.
  • 버그 수정

    • 대시보드 삭제 및 초대 API 호출 시 오류 처리 및 사용자 알림 기능이 강화되었습니다.
  • 리팩터

    • 모달 상태 관리가 단일 상태와 타입 기반으로 변경되어 코드 간결성과 확장성이 향상되었습니다.
    • 여러 컴포넌트에서 불필요한 props 제거 및 내부 스토어 활용으로 상태 관리 일관성이 개선되었습니다.
    • 기존 단순 컴포넌트 일부가 삭제되고, 기능이 통합된 새 컴포넌트로 대체되었습니다.
    • 레이아웃 구조가 간소화되고, 일부 컴포넌트가 재구성되었습니다.
  • 스타일

    • 대시보드 편집 페이지와 버튼 영역의 레이아웃 및 스타일이 미세 조정되었습니다.
  • 테스트/기타

    • 목업 멤버 데이터에 신규 멤버가 추가되었습니다.

yuj2n added 30 commits June 18, 2025 19:26
@yuj2n yuj2n added this to the 2차 구현 기간 milestone Jun 19, 2025
@yuj2n yuj2n self-assigned this Jun 19, 2025
@yuj2n yuj2n added the ✨Feat 기능 개발 label Jun 19, 2025
@yuj2n yuj2n linked an issue Jun 19, 2025 that may be closed by this pull request
1 task
@yuj2n yuj2n added 🎨Style UI, 스타일 관련 수정 💄Modify 자잘한 수정 🔧Chore 설정, 빌드 변경 labels Jun 19, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jun 19, 2025

Warning

Rate limit exceeded

@yuj2n has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 59 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2081282 and 322800a.

📒 Files selected for processing (1)
  • src/app/shared/components/common/header/RightHeaderNav.tsx (1 hunks)

"""

Walkthrough

대시보드 편집 페이지와 관련된 컴포넌트 구조가 리팩토링되었습니다. 기존의 단순 플레이스홀더 컴포넌트가 삭제되고, 실제 기능을 가진 컴포넌트로 대체되었습니다. 모달, 대시보드 선택, 헤더, 사이드바, 초대, 멤버 관리 등 다양한 영역에서 전역 상태 관리와 UI, API 연동이 개선되었습니다.

Changes

파일/경로 그룹 변경 요약
src/app/dashboard/[id]/edit/components/EditInfo.tsx
src/app/dashboard/[id]/edit/components/EditInvitation.tsx
src/app/dashboard/[id]/edit/components/EditMember.tsx
대시보드 편집 관련 단순 플레이스홀더 컴포넌트 3개 삭제
src/app/dashboard/[id]/edit/page.tsx Next.js 네비게이션 훅 도입, 삭제 버튼 영역 리팩토링, DeleteDashboardButton 컴포넌트 사용
src/app/features/dashboard/components/edit/EditInfo.tsx 전역 대시보드 선택 store 연동, 대시보드 수정(put) API 연동, UI 및 에러 처리 개선
src/app/features/dashboard/components/edit/EditInvitation.tsx 초대 목록 페이지네이션 추가, 초대 버튼 모달 연동, UI 개선
src/app/features/dashboard/components/edit/EditMember.tsx 멤버 목록 페이지네이션 추가, UI 개선
src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx 대시보드 삭제 기능 컴포넌트 신규 추가, 삭제 API 연동, 삭제 후 이동 처리
src/app/features/dashboard/api/invitation.ts 사용자 초대 API 함수(inviteUser) 신규 추가
src/app/features/dashboard/components/edit/mockMember.js mockMembers에 "Jenny" 멤버 추가
src/app/shared/components/common/Avatar.tsx
src/app/shared/components/common/UserInfo.tsx
사용자 정보 props 제거, 내부적으로 인증 store에서 사용자 정보 조회, 컴포넌트 시그니처 변경
src/app/shared/components/common/header/Header.tsx 헤더 좌/우 영역 분리, LeftHeaderContent·RightHeaderNav 컴포넌트 도입, 코드 정리
src/app/shared/components/common/header/LeftHeaderContent.tsx 선택된 대시보드 정보 표시 컴포넌트 신규 추가
src/app/shared/components/common/header/RightHeaderNav.tsx 대시보드 편집/초대 네비게이션 및 모달 연동 컴포넌트 신규 추가
src/app/shared/components/common/header/UserDropdown.tsx 로그아웃 로직 개선 (useAuth 훅 사용)
src/app/shared/components/common/GlobalModalRender.tsx 전역 모달 렌더러 컴포넌트 신규 추가
src/app/shared/components/common/modal/CreateDashboardModal.tsx 모달 상태 관리 방식 전환(모달 타입 기반), 모달 오픈/닫기 로직 통합
src/app/shared/components/common/modal/CreateInvitationModal.tsx 초대 모달 컴포넌트 신규 추가, 이메일 초대 API 연동, 토스트 알림
src/app/shared/components/common/sidebar/Sidebar.tsx 대시보드 선택 전역 store 연동, 에러 처리 개선, 모달 오픈 방식 변경
src/app/shared/hooks/useDashboard.ts 대시보드 목록 fetch 방식 useQuery 기반으로 리팩토링, 상태 관리 단순화
src/app/shared/store/useModalStore.ts 모달 상태 관리 리팩토링(모달 타입 기반), 타입 import 경로 변경
src/app/shared/store/useSelectedDashboardStore.ts 선택된 대시보드 전역 store 신규 추가
src/app/shared/types/dashboard.ts ModalState 타입 제거
src/app/shared/types/modal.ts ModalType, ModalState 타입 신규 추가
src/app/layout.tsx GlobalModalRenderer 컴포넌트 추가
src/app/features/auth/store/useAuthStore.ts 불필요한 공백 한 줄 추가
src/app/tester/page.tsx 모달 오픈 방식 변경, Header 컴포넌트 prop 추가

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Sidebar
    participant useSelectedDashboardStore
    participant DashboardEditPage
    participant DeleteDashboardButton
    participant API

    User->>Sidebar: 대시보드 클릭
    Sidebar->>useSelectedDashboardStore: setSelectedDashboard(dashboard)
    Sidebar->>DashboardEditPage: 이동 (/dashboard/:id/edit)
    DashboardEditPage->>DeleteDashboardButton: dashboardId 전달
    User->>DeleteDashboardButton: 삭제 버튼 클릭
    DeleteDashboardButton->>User: 삭제 확인 다이얼로그
    User->>DeleteDashboardButton: 확인
    DeleteDashboardButton->>API: DELETE /teams/:teamId/dashboards/:dashboardId
    API-->>DeleteDashboardButton: 성공/실패 응답
    DeleteDashboardButton->>DashboardEditPage: /dashboard로 이동
Loading
sequenceDiagram
    participant User
    participant RightHeaderNav
    participant useModalStore
    participant GlobalModalRenderer
    participant CreateInvitationModal
    participant API

    User->>RightHeaderNav: 초대 버튼 클릭
    RightHeaderNav->>useModalStore: openModal('invite')
    useModalStore->>GlobalModalRenderer: modalType 변경 감지
    GlobalModalRenderer->>CreateInvitationModal: 렌더링
    User->>CreateInvitationModal: 이메일 입력, 초대 전송
    CreateInvitationModal->>API: POST /teams/:teamId/dashboards/:dashboardId/invitations
    API-->>CreateInvitationModal: 응답
    CreateInvitationModal->>useModalStore: closeModal()
Loading

Possibly related PRs

Suggested labels

♻️Refactor, 🐛Fix

Suggested reviewers

  • LeeCh0129
  • dkslel1225

Poem

🐇
새로운 대시보드, 모달이 춤추네
초대와 삭제, 버튼이 반짝
전역 상태로 더 똑똑해진 나
페이지네이션도 척척,
헤더와 사이드바도 싹 바뀌었지!
코드는 깔끔, 토끼는 신나
리팩토링의 봄, 오늘도 만세!

"""

✨ Finishing Touches
  • 📝 Generate Docstrings

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

@yuj2n yuj2n changed the base branch from main to develop June 19, 2025 12:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (1)
src/app/shared/components/common/UserInfo.tsx (1)

7-11: 타입 정의와 실제 구현이 일치하지 않습니다.

UserInfoProps에서 nickname과 imageUrl이 여전히 정의되어 있지만, 실제 함수에서는 사용되지 않습니다. 타입 정의를 업데이트해야 합니다.

다음과 같이 타입 정의를 수정하세요:

type UserInfoProps = {
-  nickname: string
-  imageUrl?: string | null
  size?: number
}
🧹 Nitpick comments (16)
src/app/features/dashboard/api/invitation.ts (1)

4-7: 타입 정의가 명확합니다.

InvitationRequest 타입이 적절하게 정의되었으나, dashboardId가 number | string 타입으로 정의된 이유를 확인해보세요. 일관성을 위해 단일 타입 사용을 고려해보세요.

src/app/shared/components/common/GlobalModalRender.tsx (1)

8-14: 모달 렌더링 로직이 잘 구현되었습니다.

조건부 렌더링이 명확하고 적절합니다. 향후 모달 타입이 추가될 경우를 대비해 switch 문이나 객체 매핑 방식으로 리팩토링을 고려해볼 수 있습니다.

향후 확장성을 위한 리팩토링 예시:

export default function GlobalModalRenderer() {
  const { modalType } = useModalStore()

+  const modalComponents = {
+    invite: CreateInvitationModal,
+    createDashboard: CreateDashboardModal,
+  }
+
+  const ModalComponent = modalComponents[modalType]
+  return ModalComponent ? <ModalComponent /> : null

-  if (modalType === 'invite') return <CreateInvitationModal />
-  if (modalType === 'createDashboard') return <CreateDashboardModal />
-  return null
}
src/app/tester/page.tsx (1)

43-43: 모달 타입 문자열의 타입 안전성 개선 제안

openModal('createDashboard')에서 문자열 리터럴을 직접 사용하고 있어 오타 위험이 있습니다. 모달 타입을 상수나 enum으로 관리하는 것을 고려해보세요.

+const MODAL_TYPES = {
+  CREATE_DASHBOARD: 'createDashboard',
+  INVITE: 'invite'
+} as const
+
-onClick={() => openModal('createDashboard')}
+onClick={() => openModal(MODAL_TYPES.CREATE_DASHBOARD)}
src/app/shared/components/common/header/RightHeaderNav.tsx (1)

29-35: 모달 타입 문자열 상수화 권장

'invite' 문자열 리터럴을 직접 사용하여 오타 위험이 있습니다.

모달 타입을 상수로 정의하여 타입 안전성을 개선하는 것을 권장합니다:

+const MODAL_TYPES = {
+  INVITE: 'invite'
+} as const
+
-onClick={() => openModal('invite')}
+onClick={() => openModal(MODAL_TYPES.INVITE)}
src/app/shared/types/modal.ts (1)

1-2: 'none' 문자열 사용 결정을 재검토해보세요.

null 대신 'none' 문자열을 사용하는 이유가 "타입이 다르기 때문"이라고 되어 있지만, TypeScript에서는 string | null 유니온 타입을 완전히 지원합니다. null이 "모달이 열리지 않은 상태"를 더 명확하게 표현할 수 있습니다.

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

-// null은 타입이 다르기 때문에 'none'으로 처리
-type ModalType = 'invite' | 'createDashboard' | 'none'
+type ModalType = 'invite' | 'createDashboard' | null
src/app/shared/store/useModalStore.ts (1)

4-4: 임포트 경로의 일관성을 확인해주세요.

다른 파일들에서는 @store/useModalStore 형태로 임포트하는데, 여기서는 @/types/modal을 사용합니다. 경로 별칭 사용의 일관성을 확인해주세요.

src/app/features/dashboard/components/edit/EditInvitation.tsx (2)

44-46: 페이지 정보 표시 로직을 확인해주세요.

현재 {totalPages} 페이지 중 {currentPage}로 표시되고 있는데, 일반적으로는 {currentPage} / {totalPages} 또는 {currentPage}페이지 (총 {totalPages}페이지) 형태가 더 직관적입니다.

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

-              {totalPages} 페이지 중 {currentPage}
+              {currentPage} / {totalPages}

95-114: 동적 스타일링과 컴포넌트 사용이 적절합니다.

isLast 조건부 스타일링과 UserInfo 컴포넌트 사용이 깔끔하게 구현되어 있습니다. 다만 key={index}를 두 번 사용하고 있는 점이 불필요합니다.

                  <UserInfo
-                    key={index}
                    nickname={member.nickname}
                    imageUrl={member.imageUrl}
                  />
src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx (4)

18-18: 프로덕션 환경에서 디버그 로그를 제거하세요.

프로덕션 환경에서는 불필요한 콘솔 로그가 노출되지 않도록 제거해야 합니다.

-  console.log('DeleteDashboardButton 렌더됨:', dashboardId)

21-25: 사용자 경험을 개선하기 위해 커스텀 확인 모달을 고려하세요.

브라우저의 기본 confirm() 대화상자는 스타일링이 제한적이고 사용자 경험이 일관되지 않을 수 있습니다. 전역 모달 시스템을 활용한 커스텀 확인 모달을 고려해보세요.


41-50: 에러 표시 방식을 개선하세요.

브라우저의 기본 alert()은 사용자 경험이 좋지 않습니다. 토스트 알림이나 인라인 에러 메시지 등 더 나은 에러 표시 방식을 고려해보세요.


56-66: 버튼 스타일링과 접근성을 확인하세요.

Text-black 클래스가 실제로 검은색 텍스트를 렌더링하는 경우, 배경색에 따라 가독성 문제가 발생할 수 있습니다. 또한 삭제 버튼의 경우 위험한 액션임을 나타내는 시각적 피드백(예: 빨간색)을 제공하는 것이 좋습니다.

-      className={`Text-black my-8 rounded-8 font-semibold transition-opacity ${
+      className={`text-white bg-red-500 hover:bg-red-600 my-8 px-4 py-2 rounded-8 font-semibold transition-colors ${
         isDeleting ? 'cursor-not-allowed opacity-50' : 'hover:opacity-90'
       }`}
src/app/features/dashboard/components/edit/EditMember.tsx (1)

7-7: 실제 API 연동을 완료하세요.

현재 목 데이터를 사용하고 있습니다. API 연동을 완료하여 실제 멤버 데이터를 사용하도록 개선이 필요합니다.

실제 API 연동을 위한 코드 작성을 도와드릴 수 있습니다. 새로운 이슈를 생성하여 이 작업을 추적하시겠습니까?

src/app/shared/hooks/useDashboard.ts (1)

23-23: staleTime 값을 상수로 관리하는 것을 고려하세요.

현재 하드코딩된 1분(60초) staleTime을 상수로 분리하면 유지보수성이 향상됩니다.

+const DASHBOARD_CACHE_STALE_TIME = 1000 * 60 // 1분
+
 export const useDashboard = () => {
   return useQuery({
     queryKey: ['dashboards'],
     queryFn: async () => {
       // ... existing code
     },
-    staleTime: 1000 * 60, // 1분간은 stale 처리 안함
+    staleTime: DASHBOARD_CACHE_STALE_TIME,
   })
 }
src/app/features/dashboard/components/edit/EditInfo.tsx (2)

54-55: 폼 검증을 강화하는 것을 고려하세요.

현재 기본적인 필수 필드 검증만 수행하고 있습니다. 제목 길이 제한, 특수문자 검증 등 추가적인 검증을 고려해보세요.

   const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault()
 
-    if (!formData.title || !formData.color) return
+    if (!formData.title?.trim() || !formData.color) {
+      alert('제목과 색상을 모두 입력해주세요.')
+      return
+    }
+    
+    if (formData.title.trim().length > 30) {
+      alert('제목은 30자 이하로 입력해주세요.')
+      return
+    }

77-77: 수정 완료 후 네비게이션 경로를 재검토하세요.

수정 완료 후 같은 편집 페이지로 다시 이동하는 것이 사용자에게 혼란을 줄 수 있습니다. 대시보드 상세 페이지나 목록 페이지로 이동하는 것을 고려해보세요.

-      // 성공 시 상세 페이지 이동
-      router.push(`/dashboard/${data.id}/edit`)
+      // 성공 시 대시보드 상세 페이지로 이동
+      router.push(`/dashboard/${data.id}`)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a644776 and 2a1c7e4.

⛔ Files ignored due to path filters (4)
  • public/images/next-disabled.png is excluded by !**/*.png
  • public/images/next.png is excluded by !**/*.png
  • public/images/prev-disabled.png is excluded by !**/*.png
  • public/images/prev.png is excluded by !**/*.png
📒 Files selected for processing (28)
  • src/app/dashboard/[id]/edit/components/EditInfo.tsx (0 hunks)
  • src/app/dashboard/[id]/edit/components/EditInvitation.tsx (0 hunks)
  • src/app/dashboard/[id]/edit/components/EditMember.tsx (0 hunks)
  • src/app/dashboard/[id]/edit/page.tsx (2 hunks)
  • src/app/features/auth/store/useAuthStore.ts (1 hunks)
  • src/app/features/dashboard/api/invitation.ts (1 hunks)
  • src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx (1 hunks)
  • src/app/features/dashboard/components/edit/EditInfo.tsx (3 hunks)
  • src/app/features/dashboard/components/edit/EditInvitation.tsx (4 hunks)
  • src/app/features/dashboard/components/edit/EditMember.tsx (2 hunks)
  • src/app/features/dashboard/components/edit/mockMember.js (1 hunks)
  • src/app/layout.tsx (2 hunks)
  • src/app/shared/components/common/Avatar.tsx (2 hunks)
  • src/app/shared/components/common/GlobalModalRender.tsx (1 hunks)
  • src/app/shared/components/common/UserInfo.tsx (2 hunks)
  • src/app/shared/components/common/header/Header.tsx (1 hunks)
  • src/app/shared/components/common/header/LeftHeaderContent.tsx (1 hunks)
  • src/app/shared/components/common/header/RightHeaderNav.tsx (1 hunks)
  • src/app/shared/components/common/header/UserDropdown.tsx (1 hunks)
  • src/app/shared/components/common/modal/CreateDashboardModal.tsx (5 hunks)
  • src/app/shared/components/common/modal/CreateInvitationModal.tsx (1 hunks)
  • src/app/shared/components/common/sidebar/Sidebar.tsx (3 hunks)
  • src/app/shared/hooks/useDashboard.ts (1 hunks)
  • src/app/shared/store/useModalStore.ts (1 hunks)
  • src/app/shared/store/useSelectedDashboardStore.ts (1 hunks)
  • src/app/shared/types/dashboard.ts (0 hunks)
  • src/app/shared/types/modal.ts (1 hunks)
  • src/app/tester/page.tsx (3 hunks)
💤 Files with no reviewable changes (4)
  • src/app/shared/types/dashboard.ts
  • src/app/dashboard/[id]/edit/components/EditInfo.tsx
  • src/app/dashboard/[id]/edit/components/EditInvitation.tsx
  • src/app/dashboard/[id]/edit/components/EditMember.tsx
🧰 Additional context used
🧬 Code Graph Analysis (15)
src/app/layout.tsx (1)
src/app/shared/components/common/GlobalModalRender.tsx (1)
  • GlobalModalRenderer (8-14)
src/app/shared/components/common/header/UserDropdown.tsx (1)
src/app/features/auth/hooks/useAuth.ts (1)
  • useAuth (5-28)
src/app/shared/components/common/header/RightHeaderNav.tsx (3)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/shared/store/useSelectedDashboardStore.ts (1)
  • useSelectedDashboardStore (10-15)
src/app/shared/lib/cn.ts (1)
  • cn (4-6)
src/app/shared/components/common/header/LeftHeaderContent.tsx (1)
src/app/shared/store/useSelectedDashboardStore.ts (1)
  • useSelectedDashboardStore (10-15)
src/app/shared/components/common/header/Header.tsx (3)
src/app/shared/components/common/header/LeftHeaderContent.tsx (1)
  • LeftHeaderContent (6-28)
src/app/shared/components/common/header/RightHeaderNav.tsx (1)
  • RightHeaderNav (10-43)
src/app/shared/components/common/modal/CreateInvitationModal.tsx (1)
  • CreateInvitationModal (11-86)
src/app/tester/page.tsx (3)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/shared/components/common/header/Header.tsx (1)
  • Header (10-34)
src/app/shared/components/common/sidebar/Sidebar.tsx (1)
  • Sidebar (16-83)
src/app/shared/store/useSelectedDashboardStore.ts (1)
src/app/shared/types/dashboard.ts (1)
  • Dashboard (2-10)
src/app/shared/components/common/modal/CreateDashboardModal.tsx (2)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/shared/constants/colors.ts (1)
  • DASHBOARD_COLORS (3-9)
src/app/shared/components/common/Avatar.tsx (2)
src/app/features/auth/store/useAuthStore.ts (1)
  • useAuthStore (6-21)
src/app/shared/lib/getColor.ts (1)
  • getColor (1-5)
src/app/shared/components/common/sidebar/Sidebar.tsx (5)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/shared/hooks/useDashboard.ts (1)
  • useDashboard (9-25)
src/app/shared/store/useSelectedDashboardStore.ts (1)
  • useSelectedDashboardStore (10-15)
src/app/shared/types/dashboard.ts (1)
  • Dashboard (2-10)
src/app/shared/components/common/sidebar/CreateDashboardButton.tsx (1)
  • CreateDashboardButton (7-27)
src/app/shared/components/common/UserInfo.tsx (2)
src/app/features/auth/store/useAuthStore.ts (1)
  • useAuthStore (6-21)
src/app/shared/components/common/Avatar.tsx (1)
  • Avatar (31-63)
src/app/features/dashboard/components/edit/EditInfo.tsx (3)
src/app/shared/store/useSelectedDashboardStore.ts (1)
  • useSelectedDashboardStore (10-15)
src/app/shared/types/dashboard.ts (1)
  • CreateDashboardRequest (31-34)
src/app/shared/constants/colors.ts (1)
  • DASHBOARD_COLORS (3-9)
src/app/features/dashboard/components/edit/EditInvitation.tsx (2)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/shared/components/common/UserInfo.tsx (1)
  • UserInfo (13-24)
src/app/shared/store/useModalStore.ts (1)
src/app/shared/types/modal.ts (1)
  • ModalState (4-8)
src/app/shared/hooks/useDashboard.ts (1)
src/app/shared/types/dashboard.ts (1)
  • DashboardListResponse (13-17)
🔇 Additional comments (44)
src/app/features/auth/store/useAuthStore.ts (1)

3-3: 코드 포맷팅 개선이 적절합니다.

import 구문 사이의 빈 줄 추가로 가독성이 향상되었습니다.

src/app/shared/components/common/header/UserDropdown.tsx (3)

6-6: 적절한 인증 훅 import 추가

useAuth 훅을 통해 로그아웃 기능을 구현하는 것이 올바른 접근방식입니다.


12-12: 로그아웃 함수 추출이 적절합니다.

구조분해할당을 통해 필요한 logout 함수만 가져오는 것이 깔끔합니다.


19-21: 로그아웃 기능 구현이 완성되었습니다.

기존의 플레이스홀더 console.log를 실제 로그아웃 로직으로 대체하고 홈페이지로 리디렉션하는 것이 적절합니다. 사용자 경험 측면에서도 로그아웃 후 메인 페이지로 이동하는 것이 자연스럽습니다.

src/app/shared/components/common/UserInfo.tsx (2)

13-16: 중앙집중식 상태 관리로의 전환이 적절합니다.

useAuthStore를 통해 사용자 데이터를 직접 가져오는 방식으로 리팩토링한 것이 좋습니다. user가 없을 때 null을 반환하는 방어적 프로그래밍도 적절합니다.


20-22: Avatar 컴포넌트 사용법이 일관성 있게 개선되었습니다.

Avatar 컴포넌트도 내부적으로 useAuthStore를 사용하도록 리팩토링되어 props 전달이 불필요해진 것이 적절합니다. user.nickname을 직접 참조하는 것도 올바릅니다.

src/app/shared/components/common/Avatar.tsx (2)

31-37: 일관성 있는 리팩토링 패턴이 적용되었습니다.

UserInfo 컴포넌트와 동일한 패턴으로 useAuthStore를 활용하여 중앙집중식 상태 관리를 구현했습니다. 타입 정의도 깔끔하게 size만 포함하고 있어 적절하며, user가 없을 때의 방어적 처리도 잘 구현되어 있습니다.


38-47: 사용자 데이터 참조가 올바르게 구현되었습니다.

user?.profileImageUrl과 user.nickname을 직접 참조하여 이미지와 alt 텍스트를 설정하는 것이 적절합니다. 옵셔널 체이닝을 사용하여 안전하게 profileImageUrl에 접근하는 것도 좋습니다.

src/app/features/dashboard/components/edit/mockMember.js (1)

24-27: 모크 데이터 추가가 적절합니다.

페이지네이션 테스트를 위한 추가 모크 멤버 데이터가 기존 패턴과 일치하게 구현되었습니다.

src/app/layout.tsx (2)

7-7: 전역 모달 시스템 통합이 적절합니다.

GlobalModalRenderer 컴포넌트의 import가 올바르게 추가되었습니다.


27-27: 모달 렌더링 위치가 적절합니다.

GlobalModalRenderer가 Providers 내부와 Toaster 이전에 올바르게 배치되어 전역 상태에 접근하면서도 최상위 레벨에서 모달을 렌더링할 수 있습니다.

src/app/features/dashboard/api/invitation.ts (1)

11-11: 환경 변수 사용 확인이 필요합니다.

NEXT_PUBLIC_TEAM_ID 환경 변수가 클라이언트 사이드에 노출되는데, 보안상 문제가 없는지 확인해보세요.

src/app/shared/components/common/GlobalModalRender.tsx (1)

1-1: 클라이언트 컴포넌트 지시어가 적절합니다.

상태 관리 훅을 사용하는 컴포넌트에 'use client' 지시어가 올바르게 추가되었습니다.

src/app/shared/components/common/header/LeftHeaderContent.tsx (3)

1-1: 클라이언트 컴포넌트 지시어가 적절합니다.

전역 상태를 사용하는 컴포넌트에 'use client' 지시어가 올바르게 추가되었습니다.


7-9: 조기 반환 패턴이 적절합니다.

선택된 대시보드가 없을 때의 조기 반환 처리가 깔끔하고 효율적입니다.


11-27: 컴포넌트 구조와 조건부 렌더링이 잘 구현되었습니다.

대시보드 제목 표시와 크라운 아이콘의 조건부 렌더링이 적절하며, Next.js Image 컴포넌트 사용법이 올바릅니다. 접근성을 위한 alt 텍스트도 적절히 제공되었습니다.

src/app/tester/page.tsx (1)

30-30: 아래 스크립트를 실행해 Header 컴포넌트 정의 위치와 title prop 지원 여부를 다시 확인해 주세요:

#!/bin/bash
# 1. export default function Header 검색
file=$(grep -R -n "export default function Header" --include="*.tsx" . | cut -d: -f1 | head -1)

# 2. fallback: const Header 검색
if [[ -z "$file" ]]; then
  file=$(grep -R -n "const Header" --include="*.tsx" . | cut -d: -f1 | head -1)
fi

echo "Header 파일: ${file:-(찾지 못함)}"

if [[ -n "$file" ]]; then
  echo "===== 컴포넌트 선언부 ====="
  grep -R -n -m1 -C2 "function Header" --include="*.tsx" "$file" || \
  grep -R -n -m1 -C2 "const Header" --include="*.tsx" "$file"
  
  echo "===== title prop 참조 여부 ====="
  grep -R -n "props\.title\|title[: =]" --include="*.tsx" "$file" || echo "– title 관련 코드 없음 –"
else
  echo "⚠️ Header 컴포넌트 정의를 찾지 못했습니다."
fi
src/app/shared/store/useSelectedDashboardStore.ts (1)

1-16: LGTM! 깔끔한 Zustand 스토어 구현

선택된 대시보드 상태를 관리하는 Zustand 스토어가 잘 구현되었습니다. 타입 정의가 명확하고 구조가 간단하여 유지보수하기 좋습니다.

src/app/shared/components/common/modal/CreateInvitationModal.tsx (1)

22-38: 폼 처리 및 에러 핸들링이 잘 구현됨

이메일 유효성 검사, API 호출, 에러 처리 로직이 적절히 구현되어 있습니다. toast 알림을 통한 피드백도 사용자 경험에 도움이 됩니다.

src/app/dashboard/[id]/edit/page.tsx (2)

27-34: 시맨틱 및 네비게이션 개선이 우수함

div에서 button으로 변경하고 window.history.back()에서 router.back()으로 변경한 것이 접근성과 Next.js 베스트 프랙티스 관점에서 훌륭한 개선입니다.


41-44: DeleteDashboardButton 컴포넌트 통합이 적절함

대시보드 삭제 기능을 별도 컴포넌트로 분리하여 관심사 분리와 재사용성이 개선되었습니다. 스타일링과 레이아웃도 일관성 있게 적용되었습니다.

src/app/shared/components/common/header/Header.tsx (3)

4-7: 컴포넌트 모듈화가 잘 적용되었습니다.

기존 인라인 JSX를 별도 컴포넌트로 분리하여 관심사 분리와 재사용성을 개선했습니다. 임포트 구조도 깔끔하게 정리되었습니다.


15-15: LeftHeaderContent 컴포넌트 사용이 적절합니다.

관련 코드 스니펫을 보면 useSelectedDashboardStore를 통해 동적으로 대시보드 제목을 표시하고 있어 정적 텍스트에서 동적 상태 관리로 올바르게 개선되었습니다.


18-20: 모달 렌더링 구조를 확인해주세요.

CreateInvitationModal이 헤더에서 직접 렌더링되고 있는데, 관련 코드에서 GlobalModalRenderer도 존재하는 것으로 보입니다. 모달이 중복 렌더링되지 않는지 확인이 필요합니다.

다음 스크립트로 모달 렌더링 구조를 확인해주세요:

#!/bin/bash
# GlobalModalRenderer와 CreateInvitationModal의 렌더링 위치 확인
echo "=== GlobalModalRenderer 사용 위치 ==="
rg -A 3 -B 3 "GlobalModalRenderer"

echo "=== CreateInvitationModal 직접 렌더링 위치 ==="
rg -A 3 -B 3 "CreateInvitationModal"
src/app/shared/types/modal.ts (1)

4-8: ModalState 인터페이스 설계가 우수합니다.

상태와 액션을 명확히 분리하고, 타입 안전성을 보장하는 잘 설계된 인터페이스입니다.

src/app/shared/store/useModalStore.ts (1)

6-10: 모달 스토어 리팩토링이 우수합니다.

Boolean 플래그 방식에서 generic modal type으로의 전환이 확장성과 유지보수성을 크게 개선했습니다. 여러 모달을 하나의 상태로 관리하는 설계가 매우 적절합니다.

src/app/shared/components/common/modal/CreateDashboardModal.tsx (4)

14-16: 모달 상태 관리 로직이 적절합니다.

새로운 generic modal store 구조에 맞게 올바르게 업데이트되었습니다. modalType === 'createDashboard' 비교를 통한 조건부 렌더링이 명확합니다.


25-29: useEffect 의존성 배열이 올바릅니다.

isModalOpen 상태 변화에 따른 폼 리셋 로직이 적절하게 구현되어 있습니다.


31-33: 조건부 렌더링 로직이 일관됩니다.

모달이 닫힌 상태에서의 early return이 명확하고, useEffect와 일관된 조건을 사용합니다.


57-57: closeModal 호출이 일관되게 적용되었습니다.

성공 시, 백드롭 클릭 시, 취소 버튼 클릭 시 모든 케이스에서 새로운 closeModal 함수를 올바르게 사용하고 있습니다.

Also applies to: 82-82, 146-146

src/app/features/dashboard/components/edit/EditInvitation.tsx (4)

11-22: 페이지네이션 로직이 정확하게 구현되었습니다.

페이지 크기, 총 페이지 수 계산, 슬라이싱 로직이 모두 올바르게 구현되어 있습니다. 상수 분리도 좋은 설계입니다.


24-34: 페이지 네비게이션 핸들러가 안전하게 구현되었습니다.

경계 조건 검사와 함께 상태 업데이트가 안전하게 처리되고 있습니다.


47-70: 페이지네이션 버튼 구현이 우수합니다.

disabled 상태에 따른 이미지 변경과 접근성을 고려한 alt 텍스트가 잘 구현되어 있습니다.


71-86: 모달 연동이 올바르게 구현되었습니다.

Link에서 button으로 변경하여 모달 상태 관리와 통합한 설계가 적절합니다. 전역 모달 스토어를 활용한 접근 방식이 일관됩니다.

src/app/features/dashboard/components/edit/EditMember.tsx (2)

9-19: 페이지네이션 로직이 정확합니다.

페이지 크기, 시작 인덱스 계산, 슬라이싱 로직이 모두 올바르게 구현되어 있습니다.


46-67: 이미지 리소스 존재 여부를 확인하세요.

페이지네이션 버튼의 이미지 경로가 하드코딩되어 있습니다. 해당 이미지들이 실제로 존재하는지 확인해야 합니다.

다음 스크립트를 실행하여 이미지 파일들의 존재 여부를 확인해보세요:

#!/bin/bash
# 페이지네이션 버튼 이미지 파일 존재 여부 확인
echo "페이지네이션 이미지 파일 확인:"
for img in "prev-disabled.png" "prev.png" "next-disabled.png" "next.png"; do
  if [ -f "public/images/$img" ]; then
    echo "$img 존재"
  else
    echo "$img 없음"
  fi
done
src/app/shared/hooks/useDashboard.ts (1)

4-25: React Query로의 리팩토링이 잘 구현되었습니다.

수동 상태 관리에서 React Query로의 전환이 적절하게 이루어졌습니다. 캐시 키 설정, staleTime 구성, 환경변수 검증 모두 올바르게 구현되어 있습니다.

src/app/shared/components/common/sidebar/Sidebar.tsx (4)

23-26: 대시보드 선택 로직이 개선되었습니다.

전체 대시보드 객체를 전역 상태에 저장하는 방식으로 개선되어, 다른 컴포넌트에서 대시보드 정보에 쉽게 접근할 수 있게 되었습니다.


28-31: 에러 타입 안전성 처리가 잘 구현되었습니다.

Error 인스턴스 여부를 확인하여 안전하게 에러 메시지를 렌더링하는 방식이 적절합니다.


53-53: 모달 시스템 업데이트가 일관성 있게 적용되었습니다.

문자열 기반의 모달 타입 시스템으로 변경되어 타입 안전성과 확장성이 개선되었습니다.


4-6: alias import 경로 설정을 확인하세요.

@hooks, @store 등의 alias import를 사용하고 있습니다. TypeScript 설정파일에서 해당 경로 매핑이 올바르게 구성되어 있는지 확인이 필요합니다.

다음 스크립트를 실행하여 TypeScript 경로 매핑 설정을 확인해보세요:

#!/bin/bash
# TypeScript 설정에서 경로 매핑 확인
echo "tsconfig.json의 경로 매핑 확인:"
if [ -f "tsconfig.json" ]; then
  jq '.compilerOptions.paths' tsconfig.json 2>/dev/null || echo "paths 설정을 찾을 수 없습니다."
else
  echo "tsconfig.json 파일이 없습니다."
fi
src/app/features/dashboard/components/edit/EditInfo.tsx (3)

27-35: 전역 상태와의 폼 동기화가 잘 구현되었습니다.

선택된 대시보드 정보를 기반으로 폼을 초기화하는 로직이 적절하게 구현되어 있습니다.


70-77: 상태 동기화와 캐시 무효화가 올바르게 구현되었습니다.

전역 상태 업데이트와 React Query 캐시 무효화를 통해 UI 일관성을 보장하는 로직이 적절합니다.


79-89: 에러 처리가 체계적으로 구현되었습니다.

Axios 에러와 일반 에러를 구분하여 처리하는 방식이 다른 컴포넌트와 일관성 있게 적용되어 있습니다.

Comment on lines +9 to +17
export const inviteUser = async ({ email, dashboardId }: InvitationRequest) => {
const response = await api.post(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}/invitations`,
{
email,
},
)
return response.data
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리 추가를 고려해보세요.

API 함수가 잘 구조화되어 있지만, 에러 핸들링이 누락되어 있습니다. try-catch 블록이나 상위 레벨에서의 에러 처리 방식을 고려해보세요.

export const inviteUser = async ({ email, dashboardId }: InvitationRequest) => {
+  try {
    const response = await api.post(
      `/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}/invitations`,
      {
        email,
      },
    )
    return response.data
+  } catch (error) {
+    console.error('Failed to invite user:', error)
+    throw error
+  }
}
📝 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 const inviteUser = async ({ email, dashboardId }: InvitationRequest) => {
const response = await api.post(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}/invitations`,
{
email,
},
)
return response.data
}
export const inviteUser = async ({ email, dashboardId }: InvitationRequest) => {
try {
const response = await api.post(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}/invitations`,
{
email,
},
)
return response.data
} catch (error) {
console.error('Failed to invite user:', error)
throw error
}
}
🤖 Prompt for AI Agents
In src/app/features/dashboard/api/invitation.ts around lines 9 to 17, the
inviteUser function lacks error handling for the API call. Wrap the API request
inside a try-catch block to catch any errors during the post request. In the
catch block, handle or rethrow the error appropriately to ensure the calling
code can respond to failures gracefully.

Comment on lines 17 to 23
<Link
href={`/dashboard/${selectedDashboard?.id}/edit`}
className={cn(
'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
pathname === '/dashboard' && 'font-semibold',
)}
>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

null safety 및 pathname 체크 로직 개선 필요

두 가지 문제가 있습니다:

  1. selectedDashboard?.id가 null인 경우 /dashboard/undefined/edit URL이 생성됩니다.
  2. pathname 체크가 /dashboard로 되어 있지만 edit 페이지 체크가 더 적절해 보입니다.
<Link
-  href={`/dashboard/${selectedDashboard?.id}/edit`}
+  href={selectedDashboard ? `/dashboard/${selectedDashboard.id}/edit` : '#'}
  className={cn(
    'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
-    pathname === '/dashboard' && 'font-semibold',
+    pathname?.includes('/edit') && 'font-semibold',
  )}
>
📝 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
<Link
href={`/dashboard/${selectedDashboard?.id}/edit`}
className={cn(
'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
pathname === '/dashboard' && 'font-semibold',
)}
>
<Link
href={selectedDashboard ? `/dashboard/${selectedDashboard.id}/edit` : '#'}
className={cn(
'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
pathname?.includes('/edit') && 'font-semibold',
)}
>
🤖 Prompt for AI Agents
In src/app/shared/components/common/header/RightHeaderNav.tsx around lines 17 to
23, fix the null safety issue by ensuring selectedDashboard and its id exist
before constructing the href to avoid URLs like /dashboard/undefined/edit. Also,
update the pathname check to specifically verify if the current path is the edit
page (e.g., '/dashboard/[id]/edit') instead of just '/dashboard' for more
accurate styling logic.

}
}

if (!modalType) return null
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

모달 조건부 렌더링 로직 수정 필요

현재 modalType만 체크하고 있어 다른 모달이 열려도 이 컴포넌트가 렌더링될 수 있습니다.

-if (!modalType) return null
+if (modalType !== 'invite') return 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
if (!modalType) return null
if (modalType !== 'invite') return null
🤖 Prompt for AI Agents
In src/app/shared/components/common/modal/CreateInvitationModal.tsx at line 40,
the conditional rendering only checks if modalType exists, which allows this
component to render even when other modals are open. Update the condition to
specifically check if modalType matches the expected type for this modal before
rendering, ensuring it only renders when the correct modal is active.

Comment on lines +27 to +30
if (!dashboardId) {
throw new Error('대시보드 ID가 없습니다.')
}
await inviteUser({ email, dashboardId: Number(dashboardId) })
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 변환 안전성 개선 제안

Number(dashboardId) 변환이 NaN을 반환할 수 있어 런타임 오류 위험이 있습니다.

-if (!dashboardId) {
-  throw new Error('대시보드 ID가 없습니다.')
-}
-await inviteUser({ email, dashboardId: Number(dashboardId) })
+const parsedDashboardId = Number(dashboardId)
+if (!dashboardId || isNaN(parsedDashboardId)) {
+  throw new Error('유효하지 않은 대시보드 ID입니다.')
+}
+await inviteUser({ email, dashboardId: parsedDashboardId })
📝 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 (!dashboardId) {
throw new Error('대시보드 ID가 없습니다.')
}
await inviteUser({ email, dashboardId: Number(dashboardId) })
const parsedDashboardId = Number(dashboardId)
if (!dashboardId || isNaN(parsedDashboardId)) {
throw new Error('유효하지 않은 대시보드 ID입니다.')
}
await inviteUser({ email, dashboardId: parsedDashboardId })
🤖 Prompt for AI Agents
In src/app/shared/components/common/modal/CreateInvitationModal.tsx around lines
27 to 30, the code converts dashboardId to a number using Number(dashboardId)
without checking if the result is NaN, which can cause runtime errors. To fix
this, validate that dashboardId is a valid number before calling inviteUser by
parsing it safely and handling the case where the conversion fails, such as
throwing a clear error or preventing the function from proceeding.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🔭 Outside diff range comments (3)
src/app/shared/components/common/header/UserDropdown.tsx (1)

27-27: UserInfo 컴포넌트 사용법 불일치 수정 필요

UserInfo 컴포넌트가 더 이상 nickname prop을 받지 않도록 리팩터링되었는데, 여전히 "닉네임"이라는 하드코딩된 값을 전달하고 있습니다.

다음과 같이 수정하세요:

-        <div className="flex cursor-pointer items-center gap-8">
-          <UserInfo nickname="닉네임" size={36} />
-        </div>
+        <div className="flex cursor-pointer items-center gap-8">
+          <UserInfo size={36} />
+        </div>
src/app/shared/components/common/UserInfo.tsx (1)

7-11: 타입 정의와 실제 구현 불일치 수정 필요

UserInfoProps에서 nicknameimageUrl을 정의하고 있지만, 실제로는 사용하지 않습니다. 혼동을 방지하기 위해 타입 정의를 업데이트해야 합니다.

다음과 같이 수정하세요:

 type UserInfoProps = {
-  nickname: string
-  imageUrl?: string | null
   size?: number
 }
src/app/shared/components/common/modal/CreateDashboardModal.tsx (1)

35-63: 사용자 오류 피드백 개선이 필요합니다.

API 오류 발생 시 콘솔에만 로그를 남기고 있어 사용자가 문제를 인지하기 어렵습니다. 토스트 메시지나 알림을 통해 사용자에게 적절한 피드백을 제공하는 것을 권장합니다.

    } catch (error) {
      console.error('대시보드 생성 오류:', error)
+     // 사용자에게 오류 알림 추가
+     alert('대시보드 생성 중 오류가 발생했습니다. 다시 시도해주세요.')
    } finally {
🧹 Nitpick comments (22)
src/app/layout.tsx (1)

27-27: 모달 렌더링 위치가 적절합니다.

GlobalModalRendererProviders 내부의 적절한 위치에 배치되어 모든 모달이 올바르게 렌더링될 수 있습니다. 이는 모달 상태 관리의 중앙화를 통해 코드 유지보수성을 향상시키는 좋은 아키텍처 개선입니다.

src/app/shared/components/common/modal/CreateInvitationModal.tsx (1)

33-37: 에러 타입 처리를 더 안전하게 개선할 수 있습니다.

현재 에러를 AxiosError로 캐스팅하고 있는데, 더 안전한 타입 가드를 사용하는 것이 좋습니다.

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

    } catch (err: unknown) {
-      // 에러 타입 안정성을 위해 axios 에러 타입으로 캐스팅
-      const error = err as AxiosError<{ message: string }>
-      showError(error?.response?.data?.message || '초대에 실패하였습니다.')
+      if (err instanceof AxiosError) {
+        const errorMessage = err.response?.data?.message || '초대에 실패하였습니다.'
+        showError(errorMessage)
+      } else {
+        showError('초대에 실패하였습니다.')
+      }
src/app/features/dashboard/components/edit/EditInvitation.tsx (3)

11-11: 페이지 크기를 설정 가능하게 고려해보세요.

PAGE_SIZE가 하드코딩된 상수로 정의되어 있습니다. 향후 사용자 설정이나 환경 변수로 조정 가능하도록 고려해 보세요.

-const PAGE_SIZE = 5 // 페이지당 표시할 초대 내역 수
+const PAGE_SIZE = Number(process.env.NEXT_PUBLIC_INVITATION_PAGE_SIZE) || 5

9-9: Mock 데이터 사용에 대한 TODO 추가 권장

현재 mockMembers를 사용하고 있는데, 실제 API 연동을 위한 TODO 주석을 추가하는 것이 좋겠습니다.

+// TODO: Replace mockMembers with actual API call
import { mockMembers } from './mockMember'

Also applies to: 95-95


11-34: 페이지네이션 로직이 올바르게 구현되었습니다.

경계값 체크와 상태 관리가 적절하게 되어 있습니다. 향후 페이지 크기를 설정 가능하게 만드는 것을 고려해보세요.

src/app/features/dashboard/components/edit/EditInfo.tsx (3)

78-88: 에러 핸들링 개선

Axios 에러 타입 구분과 적절한 에러 메시지 처리가 구현되어 있습니다. 다만 alert 대신 토스트 메시지나 더 나은 UX를 제공하는 에러 표시 방법을 고려해 보세요.

-        alert(message)
+        // TODO: Replace with toast notification or error state
+        alert(message)

78-88: 에러 처리가 개선되었지만 사용자 경험을 더 향상시킬 수 있습니다.

Axios 에러 검증과 상세 메시지 처리가 좋습니다. 하지만 alert 사용보다는 더 현대적인 사용자 피드백 방식을 고려해보세요.

- alert(message)
+ // 토스트 알림이나 인라인 에러 메시지 사용 고려
+ showErrorToast(message)

78-92: 오류 처리가 크게 개선되었습니다.

Axios 오류 타입 체크와 사용자 피드백을 통해 더 나은 오류 경험을 제공합니다. 향후 토스트 메시지 시스템으로 대체하는 것을 고려해보세요.

src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx (9)

18-18: 디버깅 로그 제거 필요

프로덕션 환경에서는 console.log를 제거해야 합니다.

-  console.log('DeleteDashboardButton 렌더됨:', dashboardId)

21-25: 사용자 확인 로직

confirm을 사용한 삭제 확인 로직이 적절합니다. 향후 커스텀 모달로 개선을 고려해 볼 수 있습니다.


40-53: 에러 핸들링

Axios 에러 타입 구분과 적절한 에러 메시지 처리가 구현되어 있습니다. EditInfo 컴포넌트와 마찬가지로 alert 대신 더 나은 사용자 경험을 제공하는 방법을 고려해 보세요.


18-18: 디버깅용 콘솔 로그를 제거해주세요.

프로덕션 코드에는 디버깅용 콘솔 로그가 포함되지 않아야 합니다.

- console.log('DeleteDashboardButton 렌더됨:', dashboardId)

40-50: 에러 처리가 포괄적으로 구현되었습니다.

Axios 에러와 일반 에러를 구분하여 처리하는 것이 좋습니다. 다만 사용자 경험 개선을 위해 더 현대적인 알림 방식을 고려해보세요.

- alert(message) // 또는 showError(message) 등으로 사용자에게 표시
+ // 토스트 알림이나 모달을 통한 에러 표시 고려
+ showErrorNotification(message)

56-66: 버튼 접근성과 스타일링을 개선할 수 있습니다.

로딩 상태 관리는 잘 되어 있습니다. 접근성 향상을 위해 aria-label이나 확인 다이얼로그 대신 모달 사용을 고려해보세요.

  return (
    <button
      onClick={handleDelete}
      disabled={isDeleting}
+     aria-label="대시보드 삭제"
      className={`Text-black my-8 rounded-8 font-semibold transition-opacity ${
        isDeleting ? 'cursor-not-allowed opacity-50' : 'hover:opacity-90'
      }`}
    >
-     대시보드 삭제하기
+     {isDeleting ? '삭제 중...' : '대시보드 삭제하기'}
    </button>

1-19: 컴포넌트 구조가 적절하지만 디버깅 코드를 제거해야 합니다.

TypeScript 타이핑과 상태 관리가 잘 구현되었으나, 프로덕션 코드에서는 console.log 문을 제거해야 합니다.

-  console.log('DeleteDashboardButton 렌더됨:', dashboardId)

20-39: 삭제 확인 및 API 호출이 적절하게 구현되었습니다.

파괴적 작업에 대한 확인 절차와 적절한 HTTP 메서드 사용이 좋습니다. 향후 더 현대적인 확인 모달 UI로 개선하는 것을 고려해보세요.


40-54: 오류 처리가 포괄적으로 구현되었습니다.

타입 안전한 오류 체크와 적절한 사용자 피드백을 제공합니다. 다른 컴포넌트와의 일관성을 위해 토스트 알림 시스템 도입을 고려해보세요.

src/app/shared/hooks/useDashboard.ts (1)

23-23: 캐싱 전략 최적화 제안

1분 staleTime 설정이 적절하지만, 대시보드 데이터의 업데이트 빈도를 고려하여 조정을 검토해보세요.

실시간성이 중요한 경우 더 짧게, 정적 데이터에 가까운 경우 더 길게 설정할 수 있습니다:

-    staleTime: 1000 * 60, // 1분간은 stale 처리 안함
+    staleTime: 1000 * 60 * 5, // 5분간은 stale 처리 안함 (정적 데이터인 경우)
src/app/features/dashboard/components/edit/EditMember.tsx (2)

9-9: 페이지 크기를 설정 가능하게 만들어보세요

하드코딩된 PAGE_SIZE 대신 사용자 설정이나 환경 변수로 관리하는 것을 고려해보세요.

-const PAGE_SIZE = 4 // 페이지당 표시할 구성원 수
+const PAGE_SIZE = Number(process.env.NEXT_PUBLIC_MEMBERS_PAGE_SIZE) || 4

7-7: 실제 API 연동 준비

현재 목 데이터를 사용하고 있는데, 실제 API 연동을 위한 준비가 필요합니다.

실제 멤버 데이터를 가져오는 커스텀 훅 구현을 도와드릴까요? React Query를 활용한 페이지네이션 API 연동 코드를 생성해드릴 수 있습니다.

src/app/shared/components/common/sidebar/Sidebar.tsx (1)

75-75: 성능 최적화를 위한 핸들러 메모이제이션 고려

인라인 화살표 함수는 매 렌더링마다 새로운 함수를 생성합니다. 대시보드 목록이 많은 경우 성능에 영향을 줄 수 있습니다.

useCallback을 사용하여 핸들러를 메모이제이션하는 것을 고려해보세요:

+import { useCallback } from 'react'

+  const handleDashboardClickMemo = useCallback((dashboard: Dashboard) => {
+    setSelectedDashboard(dashboard)
+    router.push(`/dashboard/${dashboard.id}`)
+  }, [setSelectedDashboard, router])

-                onClick={() => handleDashboardClick(dashboard)}
+                onClick={() => handleDashboardClickMemo(dashboard)}
src/app/shared/components/common/modal/CreateDashboardModal.tsx (1)

58-62: 사용자 친화적인 에러 처리 개선을 고려해보세요.

현재 console.error만 사용하고 있는데, 사용자에게 시각적 피드백을 제공하는 것이 좋겠습니다.

  } catch (error) {
    console.error('대시보드 생성 오류:', error)
+   // 사용자에게 에러 메시지 표시 (토스트, 알림 등)
+   alert('대시보드 생성 중 오류가 발생했습니다. 다시 시도해주세요.')
  } finally {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a644776 and 2a1c7e4.

⛔ Files ignored due to path filters (4)
  • public/images/next-disabled.png is excluded by !**/*.png
  • public/images/next.png is excluded by !**/*.png
  • public/images/prev-disabled.png is excluded by !**/*.png
  • public/images/prev.png is excluded by !**/*.png
📒 Files selected for processing (28)
  • src/app/dashboard/[id]/edit/components/EditInfo.tsx (0 hunks)
  • src/app/dashboard/[id]/edit/components/EditInvitation.tsx (0 hunks)
  • src/app/dashboard/[id]/edit/components/EditMember.tsx (0 hunks)
  • src/app/dashboard/[id]/edit/page.tsx (2 hunks)
  • src/app/features/auth/store/useAuthStore.ts (1 hunks)
  • src/app/features/dashboard/api/invitation.ts (1 hunks)
  • src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx (1 hunks)
  • src/app/features/dashboard/components/edit/EditInfo.tsx (3 hunks)
  • src/app/features/dashboard/components/edit/EditInvitation.tsx (4 hunks)
  • src/app/features/dashboard/components/edit/EditMember.tsx (2 hunks)
  • src/app/features/dashboard/components/edit/mockMember.js (1 hunks)
  • src/app/layout.tsx (2 hunks)
  • src/app/shared/components/common/Avatar.tsx (2 hunks)
  • src/app/shared/components/common/GlobalModalRender.tsx (1 hunks)
  • src/app/shared/components/common/UserInfo.tsx (2 hunks)
  • src/app/shared/components/common/header/Header.tsx (1 hunks)
  • src/app/shared/components/common/header/LeftHeaderContent.tsx (1 hunks)
  • src/app/shared/components/common/header/RightHeaderNav.tsx (1 hunks)
  • src/app/shared/components/common/header/UserDropdown.tsx (1 hunks)
  • src/app/shared/components/common/modal/CreateDashboardModal.tsx (5 hunks)
  • src/app/shared/components/common/modal/CreateInvitationModal.tsx (1 hunks)
  • src/app/shared/components/common/sidebar/Sidebar.tsx (3 hunks)
  • src/app/shared/hooks/useDashboard.ts (1 hunks)
  • src/app/shared/store/useModalStore.ts (1 hunks)
  • src/app/shared/store/useSelectedDashboardStore.ts (1 hunks)
  • src/app/shared/types/dashboard.ts (0 hunks)
  • src/app/shared/types/modal.ts (1 hunks)
  • src/app/tester/page.tsx (3 hunks)
💤 Files with no reviewable changes (4)
  • src/app/shared/types/dashboard.ts
  • src/app/dashboard/[id]/edit/components/EditInvitation.tsx
  • src/app/dashboard/[id]/edit/components/EditInfo.tsx
  • src/app/dashboard/[id]/edit/components/EditMember.tsx
🧰 Additional context used
🧬 Code Graph Analysis (14)
src/app/layout.tsx (1)
src/app/shared/components/common/GlobalModalRender.tsx (1)
  • GlobalModalRenderer (8-14)
src/app/shared/components/common/header/UserDropdown.tsx (1)
src/app/features/auth/hooks/useAuth.ts (1)
  • useAuth (5-28)
src/app/shared/components/common/Avatar.tsx (2)
src/app/features/auth/store/useAuthStore.ts (1)
  • useAuthStore (6-21)
src/app/shared/lib/getColor.ts (1)
  • getColor (1-5)
src/app/shared/components/common/UserInfo.tsx (2)
src/app/features/auth/store/useAuthStore.ts (1)
  • useAuthStore (6-21)
src/app/shared/components/common/Avatar.tsx (1)
  • Avatar (31-63)
src/app/shared/components/common/header/LeftHeaderContent.tsx (1)
src/app/shared/store/useSelectedDashboardStore.ts (1)
  • useSelectedDashboardStore (10-15)
src/app/shared/components/common/header/RightHeaderNav.tsx (3)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/shared/store/useSelectedDashboardStore.ts (1)
  • useSelectedDashboardStore (10-15)
src/app/shared/lib/cn.ts (1)
  • cn (4-6)
src/app/shared/components/common/modal/CreateDashboardModal.tsx (2)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/shared/constants/colors.ts (1)
  • DASHBOARD_COLORS (3-9)
src/app/shared/store/useSelectedDashboardStore.ts (1)
src/app/shared/types/dashboard.ts (1)
  • Dashboard (2-10)
src/app/tester/page.tsx (3)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/shared/components/common/header/Header.tsx (1)
  • Header (10-34)
src/app/shared/components/common/sidebar/Sidebar.tsx (1)
  • Sidebar (16-83)
src/app/shared/components/common/modal/CreateInvitationModal.tsx (3)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/features/dashboard/api/invitation.ts (1)
  • inviteUser (9-17)
src/app/shared/lib/toast.ts (2)
  • showSuccess (3-3)
  • showError (4-4)
src/app/features/dashboard/components/edit/EditInvitation.tsx (2)
src/app/shared/store/useModalStore.ts (1)
  • useModalStore (6-10)
src/app/shared/components/common/UserInfo.tsx (1)
  • UserInfo (13-24)
src/app/shared/store/useModalStore.ts (1)
src/app/shared/types/modal.ts (1)
  • ModalState (4-8)
src/app/features/dashboard/components/edit/EditInfo.tsx (3)
src/app/shared/store/useSelectedDashboardStore.ts (1)
  • useSelectedDashboardStore (10-15)
src/app/shared/types/dashboard.ts (1)
  • CreateDashboardRequest (31-34)
src/app/shared/constants/colors.ts (1)
  • DASHBOARD_COLORS (3-9)
src/app/shared/hooks/useDashboard.ts (1)
src/app/shared/types/dashboard.ts (1)
  • DashboardListResponse (13-17)
🔇 Additional comments (68)
src/app/features/dashboard/components/edit/mockMember.js (1)

24-27: 목 데이터 추가가 적절합니다.

페이지네이션 테스트를 위한 추가 멤버 데이터가 기존 구조와 일치하며, 적절히 구현되었습니다.

src/app/features/dashboard/api/invitation.ts (1)

4-7: 타입 정의가 적절합니다.

InvitationRequest 타입이 명확하게 정의되어 있어 타입 안전성을 보장합니다.

src/app/shared/types/modal.ts (2)

1-2: 주석이 유용합니다.

null 대신 'none'을 사용하는 이유에 대한 설명이 명확하여 코드 이해에 도움이 됩니다.


2-8: 타입 정의가 잘 구조화되어 있습니다.

모달 타입과 상태 관리를 위한 타입 정의가 명확하고 확장 가능한 구조로 되어 있습니다. 향후 새로운 모달 타입을 쉽게 추가할 수 있습니다.

src/app/features/auth/store/useAuthStore.ts (1)

3-3: 코드 가독성 개선입니다.

import 문 사이의 빈 줄 추가로 코드 가독성이 향상되었습니다.

src/app/layout.tsx (1)

7-7: 모달 렌더러 통합이 적절합니다.

중앙화된 모달 관리를 위한 GlobalModalRenderer 컴포넌트 import가 추가되었습니다.

src/app/shared/components/common/header/UserDropdown.tsx (1)

6-6: 로그아웃 기능 구현이 올바르게 되었습니다.

useAuth 훅을 사용하여 실제 로그아웃 기능을 구현하고 홈페이지로 리다이렉트하는 것이 적절합니다.

Also applies to: 12-12, 19-21

src/app/shared/components/common/GlobalModalRender.tsx (1)

8-14: 깔끔한 글로벌 모달 관리 구현입니다.

modalType 기반의 조건부 렌더링 구조가 명확하고, 새로운 모달 타입 추가 시 쉽게 확장할 수 있는 좋은 패턴입니다.

src/app/shared/components/common/UserInfo.tsx (1)

13-22: 전역 상태를 활용한 리팩터링이 잘 되었습니다.

useAuthStore를 사용하여 사용자 정보를 가져오고, 사용자가 없을 때 적절히 null을 반환하는 구현이 좋습니다.

src/app/shared/components/common/header/LeftHeaderContent.tsx (2)

6-27: 선택된 대시보드 표시 로직이 잘 구현되었습니다.

전역 상태를 활용한 대시보드 정보 표시와 조건부 렌더링이 적절합니다. 크라운 아이콘으로 소유자 표시하는 UX도 좋습니다.


18-24: 이미지 경로 존재 여부 확인 필요

/images/crown.png 경로가 실제로 존재하는지 확인이 필요합니다. 이미지가 없을 경우 깨진 이미지가 표시될 수 있습니다.

다음 스크립트로 이미지 파일 존재 여부를 확인해보세요:

#!/bin/bash
# 크라운 이미지 파일 존재 확인
fd -t f "crown.png" public/
src/app/shared/components/common/Avatar.tsx (2)

31-37: 전역 상태 활용한 리팩터링이 훌륭합니다.

useAuthStore를 사용하여 사용자 정보를 가져오고, 사용자가 없을 때 적절히 null을 반환하는 구현이 일관성 있고 좋습니다.


38-62: 기존 아바타 렌더링 로직이 잘 유지되었습니다.

프로필 이미지가 있을 때와 없을 때의 조건부 렌더링 로직이 올바르게 유지되면서 전역 상태의 사용자 정보를 적절히 활용하고 있습니다.

src/app/shared/store/useSelectedDashboardStore.ts (1)

1-15: 깔끔한 글로벌 상태 관리 구현이 잘 되었습니다!

Zustand를 사용한 선택된 대시보드 상태 관리가 간결하고 타입 안전하게 구현되어 있습니다. 전역 상태 관리를 통해 사이드바, 헤더, 편집 폼 간의 상태 동기화가 가능해집니다.

src/app/tester/page.tsx (3)

9-9: 모달 임포트 경로 개선이 잘 되었습니다.

모달 컴포넌트를 더 일반적인 modal 디렉토리로 이동시켜 구조가 개선되었습니다.


30-30: 헤더 컴포넌트 props 추가가 적절합니다.

title prop을 통해 헤더에 페이지별 제목을 동적으로 표시할 수 있게 되었습니다.


43-43: 새로운 모달 관리 방식이 잘 적용되었습니다.

기존의 개별 모달 상태 관리에서 통합된 modalType 기반 관리로 변경되어 더 확장 가능한 구조가 되었습니다.

src/app/shared/components/common/header/RightHeaderNav.tsx (1)

29-41: 모달 상태 기반 스타일링이 잘 구현되었습니다.

초대 버튼의 활성 상태 스타일링이 modalType을 기반으로 적절히 구현되어 있습니다.

src/app/dashboard/[id]/edit/page.tsx (3)

8-15: Next.js 네비게이션 훅 사용이 적절합니다.

useParamsuseRouter를 사용하여 더 Next.js 방식에 맞는 네비게이션 처리가 구현되었습니다.


27-34: 접근성 개선을 위한 시맨틱 요소 사용이 좋습니다.

div에서 button으로 변경하고 router.back()을 사용하여 접근성과 사용성이 개선되었습니다.


42-44: 타입 변환 검증이 필요합니다.

useParams에서 가져온 idString()으로 변환하고 있는데, DeleteDashboardButtondashboardId prop 타입을 확인해보세요.

다음 스크립트로 DeleteDashboardButton의 prop 타입을 확인해보겠습니다:

#!/bin/bash
# DeleteDashboardButton 컴포넌트의 prop 타입 확인
ast-grep --pattern $'interface $_ {
  dashboardId: $_
  $$$
}'

# 또는 타입 정의 검색
rg -A 5 "dashboardId.*:" src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx
src/app/shared/components/common/modal/CreateInvitationModal.tsx (2)

22-38: 폼 제출 로직이 잘 구현되었습니다.

이메일 검증, API 호출, 성공/실패 처리가 적절히 구현되어 있습니다.


42-86: 모달 UI 구조와 스타일링이 잘 되어 있습니다.

백드롭 클릭 처리, 폼 레이아웃, 버튼 구성이 사용자 경험을 고려하여 잘 구현되었습니다.

src/app/shared/components/common/modal/CreateDashboardModal.tsx (7)

14-15: 모달 상태 관리 개선이 잘 구현되었습니다.

useModalStore를 사용한 중앙집중식 모달 관리로 코드가 더 깔끔해졌습니다. modalType 비교를 통한 조건부 렌더링이 적절합니다.


25-29: 의존성 배열 업데이트 확인 필요

useEffect의 의존성 배열이 isModalOpen으로 업데이트되었는데, 이는 modalType === 'createDashboard'의 결과값입니다. 모달이 열릴 때마다 폼 데이터가 초기화되는 것이 의도한 동작인지 확인해 주세요.


57-57: 일관된 closeModal 사용

모든 닫기 동작에서 통일된 closeModal() 함수를 사용하여 일관성이 좋습니다.

Also applies to: 82-82, 146-146


14-15: 모달 상태 관리 개선이 잘 적용되었습니다.

기존의 특정 모달 boolean 플래그에서 범용적인 modalType 문자열 기반 관리로 전환된 것이 좋습니다. 중앙화된 모달 상태 관리가 일관성을 제공합니다.


14-16: 모달 상태 관리 구조가 잘 개선되었습니다.

중앙화된 모달 스토어를 사용하여 일관된 모달 관리를 구현했고, 조기 반환 패턴으로 불필요한 렌더링을 방지하고 있습니다.

Also applies to: 25-34


24-29: 폼 리셋 로직이 적절하게 구현되었습니다.

모달이 닫힐 때 폼 데이터와 제출 상태를 모두 초기화하여 좋은 사용자 경험을 제공합니다.


80-84: 모달 상호작용이 올바르게 구현되었습니다.

백드롭 클릭 처리와 버튼 상태 관리가 적절하게 되어 있어 좋은 사용자 경험을 제공합니다.

Also applies to: 144-150

src/app/features/dashboard/components/edit/EditInvitation.tsx (9)

24-34: 페이지네이션 로직이 올바르게 구현되었습니다.

이전/다음 버튼의 활성화/비활성화 로직과 페이지 범위 체크가 적절합니다.


72-72: 모달 연동이 적절히 구현되었습니다.

openModal('invite') 호출을 통한 초대 모달 연동이 잘 구현되어 있습니다.


104-108: UserInfo 컴포넌트 props 검증 필요

UserInfo 컴포넌트에 nicknameimageUrl를 props로 전달하고 있는데, 해당 컴포넌트가 이러한 props를 지원하는지 확인이 필요합니다.

다음 스크립트로 UserInfo 컴포넌트의 인터페이스를 확인해 주세요:

#!/bin/bash
# Description: UserInfo 컴포넌트의 props 인터페이스 확인
ast-grep --pattern 'interface UserInfoProps {
  $$$
}'

ast-grep --pattern 'type UserInfoProps = {
  $$$
}'

11-11: 페이지네이션 구현이 잘 되었습니다.

PAGE_SIZE 상수를 통한 페이지 크기 관리가 적절합니다.


9-9: 목업 데이터 사용에 대한 계획을 확인해주세요.

현재 mockMembers를 사용하고 있는데, 실제 API 연동 계획이 있는지 확인이 필요합니다.

실제 API 연동이 필요한 경우 다음과 같은 구조로 개선할 수 있습니다:

- import { mockMembers } from './mockMember'
+ import { useInvitationList } from '@/hooks/useInvitationList'

  export default function EditInvitation() {
+   const { data: members = [], isLoading } = useInvitationList()
    const { openModal } = useModalStore()
    const [currentPage, setCurrentPage] = React.useState(1)
-   const totalPages = Math.ceil(mockMembers.length / PAGE_SIZE)
+   const totalPages = Math.ceil(members.length / PAGE_SIZE)

24-34: 페이지네이션 로직이 올바르게 구현되었습니다.

이전/다음 버튼의 비활성화 상태 관리와 페이지 범위 검증이 적절합니다.


7-7: 모달 통합이 일관되게 구현되었습니다.

중앙화된 모달 스토어를 올바르게 사용하고 기존 UI 스타일을 유지하면서 기능을 개선했습니다.

Also applies to: 15-15, 71-86


44-70: 페이지네이션 UI가 접근성을 고려하여 잘 구현되었습니다.

비활성화 상태에 대한 시각적 피드백과 적절한 alt 텍스트를 제공하여 좋은 사용자 경험을 만들고 있습니다.


95-114: 멤버 목록 렌더링이 적절하게 구현되었습니다.

UserInfo 컴포넌트 활용과 조건부 스타일링이 잘 되어 있습니다. 다만 취소 버튼에 클릭 핸들러가 없는 것으로 보이는데, 추후 구현 예정인지 확인이 필요합니다.

src/app/shared/components/common/header/Header.tsx (6)

6-8: 컴포넌트 모듈화가 잘 구현되었습니다.

헤더 로직을 LeftHeaderContentRightHeaderNav로 분리하여 관심사의 분리가 잘 이루어졌습니다. CreateInvitationModal의 위치도 적절합니다.

Also applies to: 15-15, 19-20


18-23: 레이아웃 구조 개선

우측 섹션의 컴포넌트 구성이 논리적으로 잘 정리되어 있습니다. 네비게이션, 모달, 협업자 목록, 사용자 정보 순서가 자연스럽습니다.


6-8: 컴포넌트 추출을 통한 모듈화가 잘 적용되었습니다.

헤더의 복잡한 로직을 LeftHeaderContentRightHeaderNav로 분리한 것이 좋습니다. 코드 가독성과 유지보수성이 향상되었습니다.


18-23: 모달 통합과 레이아웃 구조가 적절합니다.

CreateInvitationModal의 통합과 협업자 목록, 사용자 드롭다운의 배치가 논리적으로 구성되어 있습니다.


1-9: 컴포넌트 모듈화가 잘 이루어졌습니다.

헤더 기능을 별도 컴포넌트로 분리하여 관심사 분리와 유지보수성을 향상시켰습니다.


10-34: 헤더 레이아웃 구조가 깔끔하게 유지되었습니다.

기존 스타일링과 구조를 유지하면서 컴포넌트 분리를 통해 코드의 가독성을 향상시켰습니다.

src/app/features/dashboard/components/edit/EditInfo.tsx (12)

16-18: 글로벌 상태 관리 통합이 잘 구현되었습니다.

useSelectedDashboardStore를 사용한 대시보드 상태 관리가 적절하게 구현되어 있습니다.


27-34: 선택된 대시보드 기반 폼 초기화

selectedDashboard 변경 시 폼 데이터를 적절히 초기화하는 로직이 잘 구현되어 있습니다.


59-61: 환경 변수 및 필수 데이터 검증

환경 변수와 선택된 대시보드 ID 존재 여부를 확인하는 검증 로직이 적절합니다.


70-77: 상태 업데이트 및 캐시 무효화 전략

글로벌 상태 업데이트 후 React Query 캐시 무효화를 통한 데이터 동기화 전략이 잘 구현되어 있습니다.


99-100: 동적 제목 표시

선택된 대시보드 제목을 동적으로 표시하는 로직이 적절합니다.


16-18: 글로벌 대시보드 상태 통합이 잘 구현되었습니다.

useSelectedDashboardStore를 통한 전역 상태 관리로 헤더, 사이드바, 편집 폼 간의 동기화가 개선되었습니다.


26-34: 선택된 대시보드로부터 폼 초기화 로직이 적절합니다.

selectedDashboard가 있을 때 폼 데이터를 초기화하는 로직이 올바르게 구현되었습니다.


63-77: API 호출과 상태 업데이트 로직이 개선되었습니다.

POST에서 PUT으로 변경되고, 성공 후 글로벌 상태 업데이트와 React Query 캐시 무효화가 적절히 처리되었습니다.


11-11: 글로벌 상태 통합이 효과적으로 구현되었습니다.

useSelectedDashboardStore를 통한 상태 관리와 폼 초기화 로직이 적절하게 구현되어 대시보드 편집 시 데이터 동기화가 잘 이루어집니다.

Also applies to: 16-34


36-48: 폼 핸들러가 깔끔하게 구현되었습니다.

입력값 변경과 색상 선택 처리가 적절하게 구현되어 있습니다.


51-77: API 통합과 상태 동기화가 우수하게 구현되었습니다.

PUT 요청 사용, 글로벌 상태 업데이트, React Query 캐시 무효화를 통해 완전한 데이터 동기화를 달성했습니다.


94-166: UI 렌더링이 적절하게 구현되었습니다.

동적 제목 표시와 기존 폼 구조 유지를 통해 일관된 사용자 경험을 제공합니다.

src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx (5)

30-32: 환경 변수 검증

필수 환경 변수 존재 여부 확인이 적절히 구현되어 있습니다.


34-39: API 호출 및 네비게이션

DELETE 요청과 성공 시 대시보드 목록 페이지로의 이동이 적절합니다.


57-66: 로딩 상태 UI

isDeleting 상태를 기반으로 한 버튼 비활성화와 스타일링이 적절합니다.


21-25: 삭제 확인 다이얼로그가 적절히 구현되었습니다.

confirm을 사용한 삭제 확인 로직이 실수로 인한 삭제를 방지합니다.


56-67: 버튼 구현이 접근성을 고려하여 잘 작성되었습니다.

로딩 상태 처리와 시각적 피드백이 적절하게 구현되어 좋은 사용자 경험을 제공합니다.

src/app/shared/store/useModalStore.ts (1)

1-10: 모달 상태 관리 리팩터링이 잘 구현되었습니다

기존의 boolean 기반 단일 모달 관리에서 문자열 타입 기반 다중 모달 관리로 개선되어 확장성이 크게 향상되었습니다. 제네릭한 openModal(type) 접근 방식으로 여러 모달을 하나의 스토어로 관리할 수 있게 되었습니다.

src/app/shared/hooks/useDashboard.ts (2)

4-7: React Query 도입으로 데이터 페칭이 개선되었습니다

기존의 수동 상태 관리에서 React Query의 선언적 접근 방식으로 변경되어 로딩, 에러, 캐싱 상태가 자동으로 관리됩니다. 절대 경로 import 사용도 일관성 있게 적용되었습니다.


13-15: 환경변수 검증 로직이 적절합니다

런타임에 필수 환경변수를 검증하여 명확한 에러 메시지를 제공하는 것은 좋은 방어 코드입니다.

src/app/shared/components/common/sidebar/Sidebar.tsx (3)

4-6: 절대 경로 import와 알리아스 사용이 일관성 있게 적용되었습니다

@hooks, @store 알리아스를 통해 코드 가독성과 유지보수성이 향상되었습니다.


23-26: 글로벌 상태 관리 통합이 잘 구현되었습니다

대시보드 선택 시 전역 상태에 전체 대시보드 객체를 저장하는 방식으로 변경되어, 다른 컴포넌트에서 선택된 대시보드 정보에 쉽게 접근할 수 있게 되었습니다.


28-31: 에러 타입 안전성 개선이 우수합니다

JSX 렌더링에서 발생할 수 있는 타입 불일치 문제를 사전에 방지하는 방어적 코딩이 잘 적용되었습니다.

Comment on lines +9 to +17
export const inviteUser = async ({ email, dashboardId }: InvitationRequest) => {
const response = await api.post(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}/invitations`,
{
email,
},
)
return response.data
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 핸들링과 환경 변수 검증을 추가하세요.

API 함수에 다음 개선사항을 적용하는 것을 권장합니다:

  1. 환경 변수 존재 여부 검증
  2. API 응답 타입 정의
  3. 에러 핸들링 개선
+type InvitationResponse = {
+  // API 응답 구조에 맞게 정의
+  success: boolean
+  message?: string
+}
+
-export const inviteUser = async ({ email, dashboardId }: InvitationRequest) => {
+export const inviteUser = async ({ email, dashboardId }: InvitationRequest): Promise<InvitationResponse> => {
+  if (!process.env.NEXT_PUBLIC_TEAM_ID) {
+    throw new Error('NEXT_PUBLIC_TEAM_ID 환경 변수가 설정되지 않았습니다.')
+  }
+
+  try {
     const response = await api.post(
       `/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}/invitations`,
       {
         email,
       },
     )
     return response.data
+  } catch (error) {
+    console.error('사용자 초대 중 오류 발생:', error)
+    throw error
+  }
 }
📝 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 const inviteUser = async ({ email, dashboardId }: InvitationRequest) => {
const response = await api.post(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}/invitations`,
{
email,
},
)
return response.data
}
type InvitationResponse = {
// API 응답 구조에 맞게 정의
success: boolean
message?: string
}
export const inviteUser = async (
{ email, dashboardId }: InvitationRequest
): Promise<InvitationResponse> => {
if (!process.env.NEXT_PUBLIC_TEAM_ID) {
throw new Error('NEXT_PUBLIC_TEAM_ID 환경 변수가 설정되지 않았습니다.')
}
try {
const response = await api.post(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}/invitations`,
{
email,
},
)
return response.data
} catch (error) {
console.error('사용자 초대 중 오류 발생:', error)
throw error
}
}
🤖 Prompt for AI Agents
In src/app/features/dashboard/api/invitation.ts around lines 9 to 17, the
inviteUser function lacks environment variable validation, explicit API response
typing, and error handling. Add a check to ensure
process.env.NEXT_PUBLIC_TEAM_ID is defined before making the API call and throw
a clear error if missing. Define and use a TypeScript type for the expected API
response to improve type safety. Wrap the API call in a try-catch block to
handle and log errors gracefully, rethrowing or returning a meaningful error as
appropriate.

Comment on lines 17 to 28
<Link
href={`/dashboard/${selectedDashboard?.id}/edit`}
className={cn(
'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
pathname === '/dashboard' && 'font-semibold',
)}
>
<div className="relative flex size-12">
<Image src="/images/management.png" fill alt="관리 버튼" />
</div>
관리
</Link>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

선택된 대시보드가 없을 때의 처리가 필요합니다.

selectedDashboardnull일 때 링크가 /dashboard/undefined/edit로 생성될 수 있습니다. 또한 pathname 조건이 정확하지 않을 수 있습니다.

다음과 같이 수정하는 것을 제안합니다:

      <Link
-        href={`/dashboard/${selectedDashboard?.id}/edit`}
+        href={selectedDashboard ? `/dashboard/${selectedDashboard.id}/edit` : '#'}
        className={cn(
          'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
-          pathname === '/dashboard' && 'font-semibold',
+          pathname.includes('/edit') && 'font-semibold',
+          !selectedDashboard && 'pointer-events-none opacity-50',
        )}
      >
📝 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
<Link
href={`/dashboard/${selectedDashboard?.id}/edit`}
className={cn(
'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
pathname === '/dashboard' && 'font-semibold',
)}
>
<div className="relative flex size-12">
<Image src="/images/management.png" fill alt="관리 버튼" />
</div>
관리
</Link>
<Link
href={selectedDashboard ? `/dashboard/${selectedDashboard.id}/edit` : '#'}
className={cn(
'Border-btn flex items-center gap-6 rounded-md border px-12 py-6',
pathname.includes('/edit') && 'font-semibold',
!selectedDashboard && 'pointer-events-none opacity-50',
)}
>
<div className="relative flex size-12">
<Image src="/images/management.png" fill alt="관리 버튼" />
</div>
관리
</Link>
🤖 Prompt for AI Agents
In src/app/shared/components/common/header/RightHeaderNav.tsx around lines 17 to
28, the code does not handle the case when selectedDashboard is null, causing
the link href to become /dashboard/undefined/edit. To fix this, add a
conditional check to render the Link only if selectedDashboard exists or provide
a fallback href. Also, update the pathname condition to correctly match the
current route for applying the font-semibold class.

}
}

if (!modalType) return null
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

모달 가시성 조건이 잘못되었습니다.

현재 modalType이 falsy일 때 null을 반환하고 있는데, 이는 초대 모달이 아닌 다른 모달이 열려있을 때도 이 컴포넌트가 렌더링됩니다.

다음과 같이 수정해야 합니다:

-  if (!modalType) return null
+  if (modalType !== 'invite') return 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
if (!modalType) return null
// before: only checks that some modalType exists
- if (!modalType) return null
+ // now: only render this component when modalType is exactly 'invite'
+ if (modalType !== 'invite') return null
🤖 Prompt for AI Agents
In src/app/shared/components/common/modal/CreateInvitationModal.tsx at line 40,
the condition checking modal visibility is incorrect because it returns null
whenever modalType is falsy, causing this component to render even when other
modals are open. Update the condition to specifically check if modalType matches
the expected type for the invitation modal, and only return null if it does not,
ensuring this component only renders for the invitation modal.

Comment on lines +44 to +67
<button onClick={handlePrev} disabled={currentPage === 1}>
<Image
src={
currentPage === 1
? '/images/prev-disabled.png'
: '/images/prev.png'
}
alt="이전"
width={36}
height={36}
/>
</button>
<button onClick={handleNext} disabled={currentPage === totalPages}>
<Image
src={
currentPage === totalPages
? '/images/next-disabled.png'
: '/images/next.png'
}
alt="다음"
width={36}
height={36}
/>
</button>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

페이지네이션 버튼의 접근성을 개선해보세요

키보드 네비게이션과 스크린 리더 지원을 위해 접근성 속성을 추가하는 것을 권장합니다.

-            <button onClick={handlePrev} disabled={currentPage === 1}>
+            <button 
+              onClick={handlePrev} 
+              disabled={currentPage === 1}
+              aria-label="이전 페이지로 이동"
+              type="button"
+            >
-            <button onClick={handleNext} disabled={currentPage === totalPages}>
+            <button 
+              onClick={handleNext} 
+              disabled={currentPage === totalPages}
+              aria-label="다음 페이지로 이동"
+              type="button"
+            >
🤖 Prompt for AI Agents
In src/app/features/dashboard/components/edit/EditMember.tsx around lines 44 to
67, the pagination buttons lack accessibility features. Improve accessibility by
adding appropriate ARIA attributes such as aria-label to describe the buttons'
actions, ensure the buttons are focusable for keyboard navigation, and consider
adding role or tabIndex if necessary. This will enhance screen reader support
and keyboard usability.

Comment on lines 71 to 97
<form>
<label htmlFor="title" className="Text-black mb-8 block text-16">
이름
</label>
<div className="flex flex-col gap-4">
{mockMembers.map((member, index) => (
<div
key={index}
className="Border-bottom flex items-center justify-between py-4"
>
<UserInfo
<div className="flex flex-col">
{paginationMembers.map((member, index) => {
// 해당 페이지 중 마지막 요소인 경우 border-bottom 미적용
const isLast = index === paginationMembers.length - 1
return (
<div
key={index}
nickname={member.nickname}
imageUrl={member.imageUrl}
/>
<button className="Text-btn Border-btn rounded-md px-16 py-2">
삭제
</button>
</div>
))}
className={`flex items-center justify-between py-12 ${
!isLast ? 'Border-bottom' : ''
}`}
>
<UserInfo
nickname={member.nickname}
imageUrl={member.imageUrl}
/>
<button className="Text-btn Border-btn rounded-md px-16 py-2">
삭제
</button>
</div>
)
})}
</div>
</form>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

사용하지 않는 form 태그를 제거하거나 적절히 활용하세요

현재 form 태그 안에 실제 form 기능이 없어 시맨틱이 맞지 않습니다. 멤버 삭제 기능 구현 시 적절한 form 처리를 추가하거나 불필요한 경우 제거해주세요.

-        <form>
-          <label htmlFor="title" className="Text-black mb-8 block text-16">
-            이름
-          </label>
+        <div>
+          <h3 className="Text-black mb-8 block text-16">
+            이름
+          </h3>
           <div className="flex flex-col">
             {/* ... */}
           </div>
-        </form>
+        </div>
📝 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
<form>
<label htmlFor="title" className="Text-black mb-8 block text-16">
이름
</label>
<div className="flex flex-col gap-4">
{mockMembers.map((member, index) => (
<div
key={index}
className="Border-bottom flex items-center justify-between py-4"
>
<UserInfo
<div className="flex flex-col">
{paginationMembers.map((member, index) => {
// 해당 페이지 중 마지막 요소인 경우 border-bottom 미적용
const isLast = index === paginationMembers.length - 1
return (
<div
key={index}
nickname={member.nickname}
imageUrl={member.imageUrl}
/>
<button className="Text-btn Border-btn rounded-md px-16 py-2">
삭제
</button>
</div>
))}
className={`flex items-center justify-between py-12 ${
!isLast ? 'Border-bottom' : ''
}`}
>
<UserInfo
nickname={member.nickname}
imageUrl={member.imageUrl}
/>
<button className="Text-btn Border-btn rounded-md px-16 py-2">
삭제
</button>
</div>
)
})}
</div>
</form>
<div>
<h3 className="Text-black mb-8 block text-16">
이름
</h3>
<div className="flex flex-col">
{paginationMembers.map((member, index) => {
// 해당 페이지 중 마지막 요소인 경우 border-bottom 미적용
const isLast = index === paginationMembers.length - 1
return (
<div
key={index}
className={`flex items-center justify-between py-12 ${
!isLast ? 'Border-bottom' : ''
}`}
>
<UserInfo
nickname={member.nickname}
imageUrl={member.imageUrl}
/>
<button className="Text-btn Border-btn rounded-md px-16 py-2">
삭제
</button>
</div>
)
})}
</div>
</div>
🤖 Prompt for AI Agents
In src/app/features/dashboard/components/edit/EditMember.tsx around lines 71 to
97, the form tag is used without any form functionality, which is semantically
incorrect. Either remove the form tag entirely if no form submission or input
handling is needed, or implement proper form handling such as onSubmit and input
elements if you plan to use it for member deletion or other actions.

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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2a1c7e4 and 861ed90.

📒 Files selected for processing (1)
  • src/app/shared/components/common/Dropdown/Dropdown.tsx (2 hunks)
🔇 Additional comments (2)
src/app/shared/components/common/Dropdown/Dropdown.tsx (2)

29-31: 함수 선언 방식 개선이 좋습니다.

화살표 함수에서 named function으로 변경한 것은 코드 일관성 측면에서 좋은 개선입니다.


34-34: 명시적 타입 어노테이션 추가가 좋습니다.

getWidthValue 함수에 명시적 반환 타입 string | undefined를 추가한 것은 타입 안전성을 향상시키는 좋은 개선입니다.

children: React.ReactNode // 드롭다운 내부 콘텐츠 (메뉴 아이템 등)
width?: string // Tailwind 클래스 기반의 너비 설정 (예: 'w-5', 'w-6')
align?: 'left' | 'center' | 'right' // 드롭다운 정렬 방향
className?: string // 사용자 정의 클래스
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

className 속성이 정의되었지만 사용되지 않습니다.

DropdownProps에 className 속성을 추가했지만 컴포넌트 내에서 실제로 사용되지 않고 있습니다. 이는 불완전한 구현으로 보입니다.

다음 diff를 적용하여 className을 실제로 사용하거나:

       <div
         ref={triggerRef}
         onClick={toggleOpen}
-        className="inline-block cursor-pointer"
+        className={`inline-block cursor-pointer ${className || ''}`}
       >

또는 현재 사용하지 않는다면 속성을 제거하세요:

 type DropdownProps = {
   trigger: React.ReactNode
   children: React.ReactNode
   width?: string
   align?: 'left' | 'center' | 'right'
-  className?: string
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/shared/components/common/Dropdown/Dropdown.tsx at line 10, the
className property is defined in DropdownProps but not used in the component. To
fix this, either apply the className to the root element of the Dropdown
component so it affects styling, or if it is not needed, remove the className
property from DropdownProps to avoid unused code.

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

🧹 Nitpick comments (3)
src/app/shared/components/common/header/NavItem.tsx (2)

41-51: button 요소의 접근성을 개선하세요.

button 요소에 접근성 속성을 추가하여 사용자 경험을 향상시킬 수 있습니다.

다음과 같이 접근성 속성을 추가하는 것을 고려해보세요:

      <button
        onClick={onClick}
        type="button"
        className="mr-12 inline-flex appearance-none align-middle"
+        aria-label={label}
      >

46-46: 레이아웃 일관성을 위해 스타일링을 통일하세요.

button 요소에는 mr-12 마진이 있지만 link 요소에는 없어서 레이아웃 불일치가 발생할 수 있습니다.

일관된 스타일링을 위해 다음과 같이 수정하는 것을 고려해보세요:

        className="mr-12 inline-flex appearance-none align-middle"

또는 두 경우 모두에서 마진을 제거하고 부모 컴포넌트에서 간격을 관리하세요:

-        className="mr-12 inline-flex appearance-none align-middle"
+        className="inline-flex appearance-none align-middle"

Also applies to: 55-55

src/app/dashboard/[id]/edit/layout.tsx (1)

1-11: 함수명이 용도와 맞지 않습니다.

AboutLayout 함수명이 대시보드 편집 페이지의 레이아웃이라는 실제 용도와 맞지 않습니다. DashboardEditLayout 또는 EditLayout으로 변경하는 것이 좋겠습니다.

-export default function AboutLayout({
+export default function DashboardEditLayout({
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 861ed90 and 2081282.

📒 Files selected for processing (4)
  • src/app/dashboard/[id]/edit/layout.tsx (1 hunks)
  • src/app/dashboard/[id]/layout.tsx (1 hunks)
  • src/app/shared/components/common/header/NavItem.tsx (1 hunks)
  • src/app/shared/components/common/header/RightHeaderNav.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/shared/components/common/header/RightHeaderNav.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/app/dashboard/[id]/layout.tsx (2)
src/app/shared/components/common/sidebar/Sidebar.tsx (1)
  • Sidebar (16-83)
src/app/shared/components/common/header/Header.tsx (1)
  • Header (10-34)
src/app/shared/components/common/header/NavItem.tsx (1)
src/app/shared/lib/cn.ts (1)
  • cn (4-6)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: eslint-check
🔇 Additional comments (5)
src/app/shared/components/common/header/NavItem.tsx (1)

26-39: LGTM: 컴포넌트 구조와 스타일링이 잘 구현되었습니다.

  • cn 유틸리티를 활용한 조건부 클래스 적용이 적절합니다
  • Next.js Image 컴포넌트의 사용법이 올바르며 접근성을 위한 alt 텍스트도 제공되었습니다
  • Tailwind CSS 클래스를 통한 일관된 스타일링이 적용되었습니다
src/app/dashboard/[id]/edit/layout.tsx (1)

7-9: 레이아웃 단순화 승인

편집 페이지에서 Header와 Sidebar를 제거하여 사용자가 편집 작업에 집중할 수 있는 깔끔한 인터페이스를 제공하는 것이 좋은 접근입니다.

src/app/dashboard/[id]/layout.tsx (3)

11-17: 레이아웃 구조 개선 승인

React Fragment를 사용한 루트 래핑과 pl-300 클래스를 통한 사이드바 공간 확보가 잘 구현되었습니다. 사이드바와 헤더가 적절히 배치되어 대시보드 페이지의 일관된 레이아웃을 제공합니다.


13-13: 아직 pl-300 클래스의 정의나 사용처가 확인되지 않았습니다. 아래 스크립트를 실행해 프로젝트 전반에서 해당 클래스가 어디에 정의·사용되는지, 그리고 Tailwind 설정에서 커스텀 spacing으로 등록되어 있는지 확인해주세요.

#!/bin/bash
# 1) pl-300 / w-300 사용처 검색 (.tsx, .css, .scss)
rg "pl-300|w-300" -g "*.tsx" -g "*.css" -g "*.scss" || echo "사용처 없음"

# 2) tailwind.config 파일에서 커스텀 spacing 확인
rg "300" -g "tailwind.config.js" -g "tailwind.config.ts" || echo "tailwind.config에 정의 없음"

12-12: tsx 파일 타입 인식 문제 해결을 위해 glob 패턴으로 전체 파일을 검색하도록 스크립트를 수정했습니다.

#!/bin/bash
# Sidebar 컴포넌트가 정의된 파일 경로 확인
rg -l "import Sidebar" --glob '*.tsx'
# 전역 상태 저장소 사용 여부 확인
rg -A5 "useSelectedDashboardStore|useModalStore" --glob '*.tsx'

Comment on lines +7 to +15
type NavItemProps = {
as?: 'link' | 'button'
href?: string
onClick?: () => void
iconSrc: string
label: string
active?: boolean
className?: string
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

조건부 props에 대한 타입 안전성을 개선하세요.

현재 타입 정의에서 as='link'일 때 href가 필수이고, as='button'일 때 onClick이 필수라는 관계가 명확하게 표현되지 않습니다.

다음과 같이 조건부 타입을 사용하여 타입 안전성을 개선할 수 있습니다:

-type NavItemProps = {
-  as?: 'link' | 'button'
-  href?: string
-  onClick?: () => void
-  iconSrc: string
-  label: string
-  active?: boolean
-  className?: string
-}
+type BaseNavItemProps = {
+  iconSrc: string
+  label: string
+  active?: boolean
+  className?: string
+}
+
+type LinkNavItemProps = BaseNavItemProps & {
+  as?: 'link'
+  href: string
+  onClick?: never
+}
+
+type ButtonNavItemProps = BaseNavItemProps & {
+  as: 'button'
+  href?: never
+  onClick: () => void
+}
+
+type NavItemProps = LinkNavItemProps | ButtonNavItemProps
🤖 Prompt for AI Agents
In src/app/shared/components/common/header/NavItem.tsx around lines 7 to 15, the
current NavItemProps type does not enforce that href is required when as is
'link' and onClick is required when as is 'button'. Refactor the type definition
to use a discriminated union with conditional types so that when as is 'link',
href is mandatory and onClick is omitted, and when as is 'button', onClick is
mandatory and href is omitted. This will improve type safety and prevent
incorrect prop combinations.

Comment on lines +54 to +56
<Link href={href} legacyBehavior>
<a className="inline-flex appearance-none align-middle">{content}</a>
</Link>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Next.js Link의 최신 API를 사용하세요.

legacyBehavior prop은 Next.js의 이전 버전과의 호환성을 위한 것으로, 최신 Next.js 버전에서는 deprecated되었습니다.

다음과 같이 최신 Next.js Link API를 사용하세요:

  return (
-    <Link href={href} legacyBehavior>
-      <a className="inline-flex appearance-none align-middle">{content}</a>
-    </Link>
+    <Link href={href} className="inline-flex appearance-none align-middle">
+      {content}
+    </Link>
  )
📝 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
<Link href={href} legacyBehavior>
<a className="inline-flex appearance-none align-middle">{content}</a>
</Link>
return (
<Link href={href} className="inline-flex appearance-none align-middle">
{content}
</Link>
)
🤖 Prompt for AI Agents
In src/app/shared/components/common/header/NavItem.tsx around lines 54 to 56,
the usage of Next.js Link includes the deprecated legacyBehavior prop. Remove
the legacyBehavior prop and update the Link component to directly wrap the
content without an anchor tag, using the latest Next.js Link API conventions.

@yuj2n yuj2n merged commit 05d9470 into develop Jun 19, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔧Chore 설정, 빌드 변경 ✨Feat 기능 개발 💄Modify 자잘한 수정 🎨Style UI, 스타일 관련 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨feat: 헤더 API 연동

2 participants