Conversation
Walkthrough로컬스토리지 기반 초대 중복 검증을 추가하고, DashboardInviteModal에 Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
src/components/editpage/InvitesEdit.tsx (2)
96-99: 불필요한 옵셔널 체이닝을 제거하세요.Line 96-98에서
inviteData가 null/undefined인지 체크하고 early return하므로, Line 99에서inviteData?.invitations의 옵셔널 체이닝(?.)은 불필요합니다.if (!inviteData) { return null; } - const invitations: Invitation[] = inviteData?.invitations; + const invitations: Invitation[] = inviteData.invitations;
120-120: 불필요한 toString() 호출을 제거하세요.
dashboardId는useParams에서 가져온 string 타입이며, Line 101-103에서 이미 null/undefined 체크를 했습니다.inviteDashboardAPI도 string을 받으므로.toString()호출은 불필요합니다.- const resData = await inviteDashboard(dashboardId.toString(), { email: inviteeEmail }); + const resData = await inviteDashboard(dashboardId, { email: inviteeEmail });src/constants/invitation.ts (1)
1-2: JSDoc 주석 형식을 표준에 맞게 수정하세요.현재 주석 형식
/**초대된 이메일 **/은 표준 JSDoc 형식이 아닙니다./**뒤와*/앞에 공백을 추가해야 합니다.-/**초대된 이메일 **/ +/** 초대된 이메일 */ export const REQUESTED_EMAIL = 'requestedEmail';src/components/dashboard/modal/DashboardInviteModal.tsx (1)
43-46: 불필요한 화살표 함수를 제거하세요.Line 44에서
setErrorMsg(() => validateInvitation(inviteeEmail))는 불필요한 화살표 함수를 사용하고 있습니다.validateInvitation은 이미 문자열을 반환하므로 직접 전달할 수 있습니다.const handleBlur = () => { - if (validateInvitation(inviteeEmail)) { - setErrorMsg(() => validateInvitation(inviteeEmail)); - return; - } + const invitationError = validateInvitation(inviteeEmail); + if (invitationError) { + setErrorMsg(invitationError); + return; + } const message = validateEmail(inviteeEmail); setErrorMsg(message); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/components/dashboard/modal/DashboardInviteModal.tsx(2 hunks)src/components/editpage/InvitesEdit.tsx(4 hunks)src/constants/invitation.ts(1 hunks)src/pages/DetailLayout.tsx(2 hunks)src/utils/validation.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts
⚙️ CodeRabbit configuration file
**/*.ts: language: "ko-KR"reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 항상 const 우선 사용 - 파일명은 camelCase로 작성 - 필요 없는 코드는 즉시 제거 (미사용 변수/함수 금지) - 함수를 제외한 유틸 파일은 default export 대신 named export 권장 [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** 형태로 작성 - 데이터 조회 함수: get / fetch / load - 데이터 수정 함수: set / update / save / remove - 이벤트 함수: handle 사용 - 계산 / 변환 함수: calculate / convert / parse / format - 조건 검사 함수: is / has / can [디렉토리 & 파일 구조] - 디렉토리명은 kebab-case 사용 - 에셋(icon, image, logos)은 index 파일을 통한 일괄 export - import 시 `@/assets/icons` 형태로 그룹 단위 import [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [주석 & 문서화] - TODO 주석 적극 활용 (급한 PR / 미구현 영역) - TSDoc 사용 가능 (컴포넌트 설명, 사용 예시 추가) - @param은 TypeScript 타입으로 설명하기 때문에 비사용
Files:
src/constants/invitation.tssrc/utils/validation.ts
**/*.tsx
⚙️ CodeRabbit configuration file
**/*.tsx: reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 const 우선 사용 - 파일명은 PascalCase로 작성 - 컴포넌트는 default export를 사용 - 파일명과 컴포넌트명은 반드시 일치 (PascalCase) - 불필요한 props는 전달하지 않기 - UI 관련 상태는 명확한 이름 사용 (isOpen, isLoading 등) [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** - UI 이벤트 핸들러는 handle* 사용 (handleClick, handleToggle 등) - 내부 로직은 calculate / convert / format 등 의미 기반 네이밍 사용 [디렉토리 & 파일 구조] - 디렉토리는 kebab-case - 컴포넌트 파일은 PascalCase - assets는 index 기반 export (Icons, Images, Logos 등) - import 시 그룹 단위 import 사용 [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [함수 선언 규칙] - 컴포넌트는 선언식 function Component() 형태 - 컴포넌트 내부의 로직 함수는 화살표 함수 사용 - 유틸성 컴포넌트가 아니면 default export [주석 & 문서화] - TODO 주석 사용 (미완성 코드 표시) - TSDoc을 컴포넌트 단위로 활용 - 예시를 포함한 간단한 설명 추가
Files:
src/pages/DetailLayout.tsxsrc/components/editpage/InvitesEdit.tsxsrc/components/dashboard/modal/DashboardInviteModal.tsx
🧬 Code graph analysis (4)
src/utils/validation.ts (1)
src/constants/invitation.ts (1)
REQUESTED_EMAIL(2-2)
src/pages/DetailLayout.tsx (1)
src/constants/invitation.ts (1)
REQUESTED_EMAIL(2-2)
src/components/editpage/InvitesEdit.tsx (2)
src/lib/apis/Invitations.ts (2)
DeleteInvitationParams(37-40)inviteDashboard(4-7)src/types/invitations.ts (1)
Invitation(18-27)
src/components/dashboard/modal/DashboardInviteModal.tsx (1)
src/utils/validation.ts (2)
validateInvitation(21-23)validateEmail(3-5)
🔇 Additional comments (3)
src/components/editpage/InvitesEdit.tsx (1)
153-165: LGTM - dashboardId 숫자 변환이 올바르게 적용되었습니다.
+dashboardId를 사용하여 string을 number로 변환하는 방식이handleCancel함수의 타입과 일치합니다.src/components/dashboard/modal/DashboardInviteModal.tsx (1)
51-54: LGTM - 유효성 검사가 올바르게 적용되었습니다.초대 중복 체크를 포함한 disabled 조건이 정확하게 구현되었습니다.
src/utils/validation.ts (1)
21-23: 유효성 검사 로직이 올바르게 구현되었습니다.
validateInvitation함수는 localStorage의 키-값 패턴과 일치하게 구현되었습니다. 다만src/pages/DetailLayout.tsxLine 37에서 지적한 대시보드 범위 이슈가 이 함수에도 영향을 미칩니다.대시보드별 초대 관리로 개선할 경우, 이 함수도 다음과 같이 수정이 필요합니다:
export const validateInvitation = (value: string, dashboardId: string) => { const storageKey = `${REQUESTED_EMAIL}_${dashboardId}`; const invitedEmails = JSON.parse(localStorage.getItem(storageKey) || '[]'); return invitedEmails.includes(value) ? '이미 요청한 이메일입니다.' : ''; };
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/components/editpage/InvitesEdit.tsx (1)
75-82: localStorage 제거 로직에서 invitations 참조 및 비교 방식 개선 제안이 콜백에서
invitations를 참조하는 방식과 비교/키 생성 방식이 다소 불안정해 보입니다.
invitations가 아래에서 선언되어 있어 스코프상 문제는 없지만, 읽는 입장에서는 “어디서 온 값인지” 추적이 어렵습니다.inviteData?.invitations를 직접 참조하거나,handleCancel호출 시 필요한 정보를 함께 넘기는 쪽이 더 명확합니다.inv.id.toString() === value.invitationId.toString()대신 두 값이 숫자 타입이라면inv.id === value.invitationId처럼 직접 비교하는 편이 타입/성능 면에서 더 낫습니다.invitation이 존재하는 조건문 안에서는invitation.invitee.email접근이 가능하므로, 과도한 옵셔널 체이닝은 제거해도 됩니다. (단, 런타임에서 비어 있거나 잘못된 이메일 값이 들어올 수 있다면typeof inviteeEmail === 'string' && inviteeEmail정도의 가드를 한 번 더 두는 것도 고려해 볼 수 있습니다.)전체적으로 다음과 같이 정리하면 가독성과 타입 안정성이 좋아질 것 같습니다:
onSuccess: (_, value: DeleteInvitationParams) => { const invitation = inviteData?.invitations.find( (inv) => inv.id === value.invitationId ); if (invitation && invitation.invitee.email) { const key = invitation.invitee.email + dashboardId; localStorage.removeItem(key); } setDeleteMessage('초대 취소가 완료되었습니다.'); openCancelModal(); refetch(); },
🧹 Nitpick comments (3)
src/components/editpage/InvitesEdit.tsx (3)
96-103: 초기 가드 위치/타입 정리 관련 소소한 리팩터링 제안
if (!inviteData) return null;이후에 바로const invitations: Invitation[] = inviteData?.invitations;를 두어 타입 단언까지 하고 있는데, 가드가 이미 있으므로 옵셔널 체이닝과 타입 명시는 생략해도 됩니다.if (!inviteData || !dashboardId) { return null; } const invitations = inviteData.invitations;처럼 두 가드를 하나로 합치고
invitations의 타입은DashboardInvitationResponse에서 자동 추론되도록 두면, 코드가 조금 더 단순해집니다. (현재 구현도 동작 상의 문제는 없어 선택 사항입니다.)
120-120:dashboardId.toString()호출은 불필요해 보입니다
dashboardId는 라우트 파라미터에서 이미 문자열로 들어오고, 위에서!dashboardId가드 후에만 이 로직이 실행되기 때문에 굳이toString()을 호출하지 않아도 됩니다.const resData = await inviteDashboard(dashboardId, { email: inviteeEmail });처럼 바로 넘기면 타입도 더 명확해집니다. (기능에는 영향 없는 선택적 리팩터링입니다.)
153-165: onCancel 핸들러 중복을 줄여 가독성 개선 가능세 곳에서 동일한 inline 핸들러
() => handleCancel(+dashboardId, invitation.id)를 반복하고 있어서, 약간의 중복이 있습니다. 아래처럼 공통 핸들러를 하나 정의해 두고 재사용하면 가독성이 좋아집니다.const handleItemCancel = (id: number) => { handleCancel(+dashboardId, id); }; // 사용처 onCancel={() => handleItemCancel(invitation.id)}현재 구현도 동작에는 문제가 없으므로, 가독성 관점에서만 고려해 보시면 좋겠습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/dashboard/modal/DashboardInviteModal.tsx(4 hunks)src/components/editpage/InvitesEdit.tsx(5 hunks)src/pages/DetailLayout.tsx(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/dashboard/modal/DashboardInviteModal.tsx
- src/pages/DetailLayout.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx
⚙️ CodeRabbit configuration file
**/*.tsx: reviews:
instructions: |
우리 팀은 다음의 코드 스타일 가이드를 따릅니다:[기본 규칙] - 변수는 const 우선 사용 - 파일명은 PascalCase로 작성 - 컴포넌트는 default export를 사용 - 파일명과 컴포넌트명은 반드시 일치 (PascalCase) - 불필요한 props는 전달하지 않기 - UI 관련 상태는 명확한 이름 사용 (isOpen, isLoading 등) [함수 네이밍 규칙] - 모든 함수는 **동사 + 목적어** - UI 이벤트 핸들러는 handle* 사용 (handleClick, handleToggle 등) - 내부 로직은 calculate / convert / format 등 의미 기반 네이밍 사용 [디렉토리 & 파일 구조] - 디렉토리는 kebab-case - 컴포넌트 파일은 PascalCase - assets는 index 기반 export (Icons, Images, Logos 등) - import 시 그룹 단위 import 사용 [상수 / 환경 변수] - SNAKE_CASE 사용 - 예: PAGE_SIZE, API_URL [함수 선언 규칙] - 컴포넌트는 선언식 function Component() 형태 - 컴포넌트 내부의 로직 함수는 화살표 함수 사용 - 유틸성 컴포넌트가 아니면 default export [주석 & 문서화] - TODO 주석 사용 (미완성 코드 표시) - TSDoc을 컴포넌트 단위로 활용 - 예시를 포함한 간단한 설명 추가
Files:
src/components/editpage/InvitesEdit.tsx
🧬 Code graph analysis (1)
src/components/editpage/InvitesEdit.tsx (2)
src/lib/apis/Invitations.ts (2)
DeleteInvitationParams(37-40)inviteDashboard(4-7)src/types/invitations.ts (1)
Invitation(18-27)
🔇 Additional comments (1)
src/components/editpage/InvitesEdit.tsx (1)
223-223:DashboardInviteModal에 dashboardId 전달은 요구사항과 잘 맞습니다중복 초대 방지를 위해 localStorage 키에
email + dashboardId를 사용하고, 모달 단에서 유효성/버튼 비활성화를 제어하려는 의도로 보입니다. 여기서dashboardId를 명시적으로 넘겨주는 구조가 요구사항(대시보드별 초대 관리)과 잘 맞습니다.특별히 수정할 부분 없이 이 변경은 그대로 괜찮아 보입니다.
✅ PR 체크리스트
1. 코드 & 기능
2. UI
3. 컨벤션
🔗 이슈 번호
✨ 작업한 내용
💁 리뷰 요청 / 코멘트
💡 참고사항
브랜치를 다른 곳에서 작업햇네요. . 이름만 다른 것이라서 기능상 문제는 없습니다!
Summary by CodeRabbit
새로운 기능
버그 수정
✏️ Tip: You can customize this high-level summary in your review settings.