-
Notifications
You must be signed in to change notification settings - Fork 2
✨feat: 헤더 API 연동 #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨feat: 헤더 API 연동 #85
Conversation
|
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 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. 📒 Files selected for processing (1)
""" Walkthrough대시보드 편집 페이지와 관련된 컴포넌트 구조가 리팩토링되었습니다. 기존의 단순 플레이스홀더 컴포넌트가 삭제되고, 실제 기능을 가진 컴포넌트로 대체되었습니다. 모달, 대시보드 선택, 헤더, 사이드바, 초대, 멤버 관리 등 다양한 영역에서 전역 상태 관리와 UI, API 연동이 개선되었습니다. Changes
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로 이동
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()
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
""" ✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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' | nullsrc/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
⛔ Files ignored due to path filters (4)
public/images/next-disabled.pngis excluded by!**/*.pngpublic/images/next.pngis excluded by!**/*.pngpublic/images/prev-disabled.pngis excluded by!**/*.pngpublic/images/prev.pngis 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컴포넌트 정의 위치와titleprop 지원 여부를 다시 확인해 주세요:#!/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 컴포넌트 정의를 찾지 못했습니다." fisrc/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 donesrc/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 파일이 없습니다." fisrc/app/features/dashboard/components/edit/EditInfo.tsx (3)
27-35: 전역 상태와의 폼 동기화가 잘 구현되었습니다.선택된 대시보드 정보를 기반으로 폼을 초기화하는 로직이 적절하게 구현되어 있습니다.
70-77: 상태 동기화와 캐시 무효화가 올바르게 구현되었습니다.전역 상태 업데이트와 React Query 캐시 무효화를 통해 UI 일관성을 보장하는 로직이 적절합니다.
79-89: 에러 처리가 체계적으로 구현되었습니다.Axios 에러와 일반 에러를 구분하여 처리하는 방식이 다른 컴포넌트와 일관성 있게 적용되어 있습니다.
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
에러 처리 추가를 고려해보세요.
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.
| 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.
| <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', | ||
| )} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
null safety 및 pathname 체크 로직 개선 필요
두 가지 문제가 있습니다:
selectedDashboard?.id가 null인 경우/dashboard/undefined/editURL이 생성됩니다.- 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.
| <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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
모달 조건부 렌더링 로직 수정 필요
현재 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.
| 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.
| if (!dashboardId) { | ||
| throw new Error('대시보드 ID가 없습니다.') | ||
| } | ||
| await inviteUser({ email, dashboardId: Number(dashboardId) }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
타입 변환 안전성 개선 제안
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.
| 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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에서
nickname과imageUrl을 정의하고 있지만, 실제로는 사용하지 않습니다. 혼동을 방지하기 위해 타입 정의를 업데이트해야 합니다.다음과 같이 수정하세요:
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: 모달 렌더링 위치가 적절합니다.
GlobalModalRenderer가Providers내부의 적절한 위치에 배치되어 모든 모달이 올바르게 렌더링될 수 있습니다. 이는 모달 상태 관리의 중앙화를 통해 코드 유지보수성을 향상시키는 좋은 아키텍처 개선입니다.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
⛔ Files ignored due to path filters (4)
public/images/next-disabled.pngis excluded by!**/*.pngpublic/images/next.pngis excluded by!**/*.pngpublic/images/prev-disabled.pngis excluded by!**/*.pngpublic/images/prev.pngis 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 네비게이션 훅 사용이 적절합니다.
useParams와useRouter를 사용하여 더 Next.js 방식에 맞는 네비게이션 처리가 구현되었습니다.
27-34: 접근성 개선을 위한 시맨틱 요소 사용이 좋습니다.
div에서button으로 변경하고router.back()을 사용하여 접근성과 사용성이 개선되었습니다.
42-44: 타입 변환 검증이 필요합니다.
useParams에서 가져온id를String()으로 변환하고 있는데,DeleteDashboardButton의dashboardIdprop 타입을 확인해보세요.다음 스크립트로 DeleteDashboardButton의 prop 타입을 확인해보겠습니다:
#!/bin/bash # DeleteDashboardButton 컴포넌트의 prop 타입 확인 ast-grep --pattern $'interface $_ { dashboardId: $_ $$$ }' # 또는 타입 정의 검색 rg -A 5 "dashboardId.*:" src/app/features/dashboard/components/edit/DeleteDashboardButton.tsxsrc/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컴포넌트에nickname과imageUrl를 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: 컴포넌트 모듈화가 잘 구현되었습니다.헤더 로직을
LeftHeaderContent와RightHeaderNav로 분리하여 관심사의 분리가 잘 이루어졌습니다.CreateInvitationModal의 위치도 적절합니다.Also applies to: 15-15, 19-20
18-23: 레이아웃 구조 개선우측 섹션의 컴포넌트 구성이 논리적으로 잘 정리되어 있습니다. 네비게이션, 모달, 협업자 목록, 사용자 정보 순서가 자연스럽습니다.
6-8: 컴포넌트 추출을 통한 모듈화가 잘 적용되었습니다.헤더의 복잡한 로직을
LeftHeaderContent와RightHeaderNav로 분리한 것이 좋습니다. 코드 가독성과 유지보수성이 향상되었습니다.
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 렌더링에서 발생할 수 있는 타입 불일치 문제를 사전에 방지하는 방어적 코딩이 잘 적용되었습니다.
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
에러 핸들링과 환경 변수 검증을 추가하세요.
API 함수에 다음 개선사항을 적용하는 것을 권장합니다:
- 환경 변수 존재 여부 검증
- API 응답 타입 정의
- 에러 핸들링 개선
+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.
| 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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
선택된 대시보드가 없을 때의 처리가 필요합니다.
selectedDashboard가 null일 때 링크가 /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.
| <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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
모달 가시성 조건이 잘못되었습니다.
현재 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.
| 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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
페이지네이션 버튼의 접근성을 개선해보세요
키보드 네비게이션과 스크린 리더 지원을 위해 접근성 속성을 추가하는 것을 권장합니다.
- <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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
사용하지 않는 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.
| <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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 // 사용자 정의 클래스 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 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
📒 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'
| type NavItemProps = { | ||
| as?: 'link' | 'button' | ||
| href?: string | ||
| onClick?: () => void | ||
| iconSrc: string | ||
| label: string | ||
| active?: boolean | ||
| className?: string | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
조건부 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.
| <Link href={href} legacyBehavior> | ||
| <a className="inline-flex appearance-none align-middle">{content}</a> | ||
| </Link> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
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.
| <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.
📌 변경 사항 개요
헤더 일부 API 연동(공동 사용자 프로필 제외)
대시보드 수정 페이지 일부 API 연동(구성원 삭제 및 이메일 초대내역 API 제외)
✨ 요약
📝 상세 내용
헤더
/dashboard/${selectedDashboard?.id}/edit페이지로 이동하도록 구현하였고 여기서selectedDashboard는 사이드 바에서 가져오는 대시보드 데이터를useSelectedDashboardStore.ts파일에서 zustand를 이용하여 전역으로 관리하도록 하여 같은 정보를 보여주는 사이드 바, 헤더, 수정 폼의 대시보드 제목이 상태 공유를 보다 편리하게 할 수 있도록 작성하였습니다.대시보드 수정 페이지
🔗 관련 이슈
🖼️ 스크린샷
20250619_210223.mp4
✅ 체크리스트
💡 참고 사항
http://localhost:3000/dashboard/15209/edit을 쳐서 접속하는 경우에 헤더의 대시보드 명이 보이지 않는 문제가 있음 -> 바로 접근 못하도록 해야할 것 같음/dashboard->/dashboard/${id}흐름의 작업이 완성되어 있지 않아/dashboard/${id}/edit로의 이동이 잘 되는지 확인하지 못하였고 따라서 해당 url의 id 값에 따라 대시보드 삭제하기 버튼 확인을 위해서는 직접 해당 대시보드의 id를 입력 후 삭제 버튼을 눌러야 확인할 수 있습니다.Summary by CodeRabbit
새로운 기능
버그 수정
리팩터
스타일
테스트/기타