-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 모임상세 정보 바인딩 작업 / date 유틸함수 refactor / 디폴트 이미지 상수 파일 생성 #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 워크스루풀 요청은 더미 데이터 기반의 밋업 페이지를 실제 데이터 구조로 전환합니다. 그룹 서비스에 참여, 취소, 삭제 작업을 추가하고, 관련 훅과 타입을 업데이트하며, 에러 처리 로직을 동적 임포트 기반으로 리팩터링합니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant User
participant MeetupPage as Meetup Page
participant MeetupButtons as MeetupButtons
participant MeetupModal as MeetupModal
participant Hook as useAttendGroup/<br/>useCancelGroup/<br/>useDeleteGroup
participant API as API Service
participant Server as Server
participant Cache as React Query Cache
User->>MeetupPage: 그룹 상세 페이지 방문
MeetupPage->>API: getGroupDetails(groupId)
API->>Server: GET /groups/{groupId}
Server-->>API: GroupDetails 반환
API-->>MeetupPage: 데이터 전달
MeetupPage->>MeetupButtons: conditions, groupId 전달
User->>MeetupButtons: 참여/취소/삭제 버튼 클릭
MeetupButtons->>MeetupModal: 모달 열기 (type, groupId)
User->>MeetupModal: 확인 클릭
alt 참여 액션
MeetupModal->>Hook: useAttendGroup 실행
Hook->>API: attendGroup({groupId})
API->>Server: POST /groups/{groupId}/attend
else 취소 액션
MeetupModal->>Hook: useCancelGroup 실행
Hook->>API: cancelGroup({groupId})
API->>Server: POST /groups/{groupId}/cancel
else 삭제 액션
MeetupModal->>Hook: useDeleteGroup 실행
Hook->>API: deleteGroup({groupId})
API->>Server: DELETE /groups/{groupId}
end
Server-->>API: 응답 반환
API->>Cache: 그룹 상세 쿼리 무효화
Cache->>MeetupPage: 캐시 업데이트
MeetupPage->>MeetupPage: 상태 갱신
MeetupModal->>User: 모달 닫음
alt 삭제의 경우
MeetupModal->>User: 홈페이지로 리다이렉트
end
예상 코드 리뷰 난이도🎯 4 (복잡함) | ⏱️ ~45분 검토 시 특별히 주의할 부분:
제안 리뷰어
시
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🎭 Playwright Report✨ E2E Test가 성공적으로 완료되었습니다. Test 요약 내용을 확인해주세요.
📊 Test Summary
📜 Test Details✅ Passed Tests (9)
|
📊 Coverage Report
📉 #180을 main에 병합하면 coverage가 Coverage 요약@@ Coverage Diff @@
## main #180 +/- ##
===========================================
- Coverage 35.89% 34.97% -0.92%
===========================================
Files 143 147 +4
Lines 5850 6016 +166
Branches 231 235 +4
===========================================
+ Hits 2100 2104 +4
+ Misses 3750 3912 +162 영향받은 파일
|
🎨 Storybook Report✅ 변경 사항이 없습니다 모든 Story가 이전 빌드와 동일합니다.
|
🚀 PR Preview Report✨ Build가 성공적으로 완료되었습니다. Preview에서 변경사항을 확인하세요.
|
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.
저만 쓸줄알고 어디 굴러다니던거 대충 넣어논거였는데 생각해보니까 쓰는 곳이 많았군요 😅
수정 감사합니다
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
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: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/meetup/[groupId]/page.tsx (1)
23-23: 디버그용console.log제거 필요.프로덕션 코드에
console.log가 남아 있습니다. 배포 전 제거해 주세요.- console.log(data);
🧹 Nitpick comments (14)
src/components/pages/meetup/meetup-banner-images/index.tsx (1)
31-31:objectFitprop은 deprecated 되었습니다.Next.js Image 컴포넌트에서
objectFitprop은 deprecated 되었습니다.styleprop이나className을 사용하세요.- <Image alt='image' draggable={false} fill objectFit='cover' src={imageUrl440x240} /> + <Image alt='image' draggable={false} fill className='object-cover' src={imageUrl440x240} />Line 37의 default 이미지에도 동일하게 적용해주세요.
src/components/pages/meetup/meetup-descriptions/description-sections/description-tags/index.tsx (1)
8-10: 더 명시적인 조건 검사를 고려해보세요.
Boolean(tags.length)보다tags.length > 0이 의도를 더 명확하게 표현합니다.- const hasTags = Boolean(tags.length); + const hasTags = tags.length > 0;src/hooks/use-group/use-group-cancel/index.ts (1)
15-18: 프로덕션 코드에서 console.log 제거를 권장합니다.성공/실패 로깅을 위해 console.log를 사용하고 있습니다. 프로덕션 환경에서는 적절한 로깅 라이브러리를 사용하거나, 사용자에게 피드백을 제공하는 토스트 메시지 등으로 대체하는 것이 좋습니다.
src/hooks/use-group/use-group-attend/index.ts (1)
15-18: 프로덕션 코드에서 console.log 제거를 권장합니다.성공/실패 로깅을 위해 console.log를 사용하고 있습니다. 프로덕션 환경에서는 적절한 로깅 라이브러리를 사용하거나, 사용자에게 피드백을 제공하는 토스트 메시지 등으로 대체하는 것이 좋습니다.
src/hooks/use-group/use-group-delete/index.ts (1)
10-14: 프로덕션 코드에서 console.log 제거를 권장합니다.성공/실패 로깅을 위해 console.log를 사용하고 있습니다. 프로덕션 환경에서는 적절한 로깅 라이브러리를 사용하거나, 사용자에게 피드백을 제공하는 토스트 메시지 등으로 대체하는 것이 좋습니다.
src/app/meetup/[groupId]/page.tsx (1)
21-21: 로딩 상태 UI 고려.
data가 없을 때null을 반환하면 레이아웃 시프트가 발생할 수 있습니다. 로딩 스피너나 스켈레톤 UI를 추가하는 것을 권장합니다.src/components/pages/meetup/meetup-members/index.tsx (2)
41-41: 리스트key로nickName대신userId사용 권장.
nickName은 고유하지 않을 수 있습니다.userId가 고유 식별자이므로key로 사용하는 것이 더 안정적입니다.- key={nickName} + key={userId}
55-55:objectFitprop은 Next.js Image에서 deprecated.Next.js 13+ 에서
objectFitprop은 deprecated되었습니다.className또는style로 대체해 주세요. 현재className에 이미rounded-full이 있으므로object-cover를 추가하면 됩니다.- className='h-16 w-16 rounded-full' + className='h-16 w-16 rounded-full object-cover' // ... - objectFit='cover'src/components/pages/meetup/meetup-descriptions/description-sections/description-profile/index.tsx (2)
4-4: Import 경로 불일치.다른 import들은
@/alias를 사용하지만 이 import는 상대 경로 형식입니다. 일관성을 위해 수정해 주세요.-import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images'; +import { DEFAULT_PROFILE_IMAGE } from '@/constants/default-images';
22-22:objectFitprop은 Next.js Image에서 deprecated.
className에object-cover를 추가하고objectFitprop을 제거해 주세요.- className='h-10 w-10 shrink-0 rounded-full' + className='h-10 w-10 shrink-0 rounded-full object-cover' // ... - objectFit='cover'src/api/service/group-service/index.ts (1)
72-86: 새 API 엔드포인트들이 잘 구현되었습니다.
attendGroup,cancelGroup,deleteGroup엔드포인트가 일관된 패턴으로 추가되었습니다.deleteGroup의 반환 타입이 명시되지 않았는데,api.delete가Promise<T>를 반환하므로 타입 안전성을 위해 명시적으로 지정하는 것이 좋습니다.deleteGroup: (payload: GroupIdPayload) => { - return api.delete(`/groups/${payload.groupId}`); + return api.delete<void>(`/groups/${payload.groupId}`); },src/components/pages/meetup/meetup-modal/index.tsx (2)
62-80:MODAL_MESSAGE의onConfirm속성이 정의되었지만 사용되지 않습니다.
onConfirm함수가 각 타입별로 정의되어 있지만handleConfirmClick에서는 if-else 체인을 사용하고 있습니다. 사용하지 않는 코드는 제거하거나, 일관성을 위해onConfirm을 활용하도록 리팩토링하세요.옵션 1: 사용하지 않는
onConfirm제거const MODAL_MESSAGE = { attend: { title: '모임에 참여하시겠어요?', description: '참여 후 바로 그룹채팅에 참여할 수 있어요!', confirm: '참여하기', - onConfirm: (attendMutate: () => void) => attendMutate(), }, cancel: { title: '모임을 정말 탈퇴하시겠어요?', description: '탈퇴 시 그룹채팅과 모임 활동이 종료돼요.', confirm: '탈퇴하기', - onConfirm: (cancelMutate: () => void) => cancelMutate(), }, delete: { title: '모임을 정말 취소하시겠어요?', description: '취소 후에는 다시 복구할 수 없어요.', confirm: '취소하기', - onConfirm: (deleteMutate: () => void) => deleteMutate(), }, };
40-56: 버튼 너비 스타일링 개선 제안
w-[50%]와gap-2를 함께 사용하면 gap으로 인해 버튼들이 컨테이너를 넘칠 수 있습니다.flex-1을 사용하면 gap을 고려하여 균등하게 분배됩니다.- <Button - className='!text-text-sm-semibold w-[50%]' + <Button + className='!text-text-sm-semibold flex-1' ... - <Button - className='!text-text-sm-bold w-[50%]' + <Button + className='!text-text-sm-bold flex-1'src/components/pages/meetup/meetup-buttons/index.tsx (1)
46-57:isHost가null일 때 렌더링 처리 개선 제안
isHost가null인 초기 상태에서 버튼이 "모임 탈퇴"로 표시될 수 있습니다. 호스트 여부가 결정될 때까지 로딩 상태를 표시하거나 버튼을 숨기는 것을 고려하세요.+ if (isHost === null) { + return <div className='sticky bottom-[56px] border-t-1 border-gray-200 bg-white px-4 py-3' />; + } + return ( <div className='sticky bottom-[56px] border-t-1 border-gray-200 bg-white px-4 py-3'>
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
constants/default-images.ts(1 hunks)next.config.ts(1 hunks)src/api/core/index.ts(1 hunks)src/api/service/group-service/index.ts(2 hunks)src/app/meetup/[groupId]/page.tsx(1 hunks)src/components/pages/meetup/meetup-banner-images/index.tsx(1 hunks)src/components/pages/meetup/meetup-buttons/index.tsx(2 hunks)src/components/pages/meetup/meetup-descriptions/description-sections/description-content/index.tsx(0 hunks)src/components/pages/meetup/meetup-descriptions/description-sections/description-detail/index.tsx(1 hunks)src/components/pages/meetup/meetup-descriptions/description-sections/description-profile/index.tsx(1 hunks)src/components/pages/meetup/meetup-descriptions/description-sections/description-progress/index.tsx(2 hunks)src/components/pages/meetup/meetup-descriptions/description-sections/description-setting/index.tsx(2 hunks)src/components/pages/meetup/meetup-descriptions/description-sections/description-tags/index.tsx(1 hunks)src/components/pages/meetup/meetup-descriptions/description-sections/description-title/index.tsx(1 hunks)src/components/pages/meetup/meetup-descriptions/description-sections/index.ts(1 hunks)src/components/pages/meetup/meetup-descriptions/index.tsx(1 hunks)src/components/pages/meetup/meetup-members/index.tsx(3 hunks)src/components/pages/meetup/meetup-modal/index.tsx(1 hunks)src/components/pages/post-meetup/fields/date-field/index.tsx(2 hunks)src/hooks/use-group/use-group-attend/index.ts(1 hunks)src/hooks/use-group/use-group-cancel/index.ts(1 hunks)src/hooks/use-group/use-group-delete/index.ts(1 hunks)src/hooks/use-group/use-group-get-details/index.ts(1 hunks)src/lib/formatDateTime.ts(2 hunks)src/types/service/group.ts(3 hunks)
💤 Files with no reviewable changes (1)
- src/components/pages/meetup/meetup-descriptions/description-sections/description-content/index.tsx
🧰 Additional context used
🧬 Code graph analysis (16)
src/components/pages/meetup/meetup-descriptions/description-sections/description-detail/index.tsx (1)
src/types/service/group.ts (1)
GetGroupDetailsResponse(125-161)
src/components/pages/meetup/meetup-descriptions/description-sections/description-title/index.tsx (2)
src/types/service/group.ts (1)
GetGroupDetailsResponse(125-161)src/components/pages/meetup/meetup-descriptions/description-sections/index.ts (1)
DescriptionTitle(6-6)
src/hooks/use-group/use-group-delete/index.ts (2)
src/types/service/group.ts (1)
GroupIdPayload(163-165)src/api/index.ts (1)
API(22-22)
src/api/service/group-service/index.ts (2)
src/types/service/group.ts (2)
GroupIdPayload(163-165)GetGroupDetailsResponse(125-161)src/api/core/index.ts (1)
api(74-100)
src/components/pages/meetup/meetup-descriptions/description-sections/description-profile/index.tsx (3)
src/types/service/group.ts (1)
GetGroupDetailsResponse(125-161)src/components/pages/meetup/meetup-descriptions/description-sections/index.ts (1)
DescriptionProfile(2-2)constants/default-images.ts (1)
DEFAULT_PROFILE_IMAGE(2-3)
src/app/meetup/[groupId]/page.tsx (3)
src/components/pages/meetup/meetup-descriptions/index.tsx (1)
MeetupDescriptions(26-49)src/components/pages/meetup/meetup-members/index.tsx (1)
MeetupMembers(19-96)src/components/pages/meetup/meetup-buttons/index.tsx (1)
MeetupButtons(22-77)
src/hooks/use-group/use-group-cancel/index.ts (3)
src/types/service/group.ts (1)
GroupIdPayload(163-165)src/api/index.ts (1)
API(22-22)src/lib/query-key/query-key-group/index.ts (1)
groupKeys(1-10)
src/hooks/use-group/use-group-get-details/index.ts (1)
src/types/service/group.ts (1)
GroupIdPayload(163-165)
src/components/pages/meetup/meetup-members/index.tsx (3)
src/types/service/group.ts (1)
GetGroupDetailsResponse(125-161)src/components/pages/meetup/index.ts (1)
MeetupMembers(4-4)src/components/icon/index.tsx (1)
Icon(55-61)
src/components/pages/meetup/meetup-descriptions/description-sections/description-tags/index.tsx (1)
src/types/service/group.ts (1)
GetGroupDetailsResponse(125-161)
src/components/pages/meetup/meetup-descriptions/index.tsx (7)
src/types/service/group.ts (1)
GetGroupDetailsResponse(125-161)src/components/pages/meetup/meetup-descriptions/description-sections/description-profile/index.tsx (1)
DescriptionProfile(12-33)src/components/pages/meetup/meetup-descriptions/description-sections/description-title/index.tsx (1)
DescriptionTitle(7-13)src/components/pages/meetup/meetup-descriptions/description-sections/description-tags/index.tsx (1)
DescriptionTags(7-21)src/components/pages/meetup/meetup-descriptions/description-sections/description-detail/index.tsx (1)
DescriptionDetail(7-13)src/components/pages/meetup/meetup-descriptions/description-sections/description-setting/index.tsx (1)
DescriptionSetting(9-24)src/components/pages/meetup/meetup-descriptions/description-sections/description-progress/index.tsx (1)
DescriptionProgress(12-37)
src/components/pages/meetup/meetup-modal/index.tsx (3)
src/hooks/use-group/use-group-attend/index.ts (1)
useAttendGroup(7-22)src/hooks/use-group/use-group-cancel/index.ts (1)
useCancelGroup(7-22)src/hooks/use-group/use-group-delete/index.ts (1)
useDeleteGroup(6-18)
src/hooks/use-group/use-group-attend/index.ts (3)
src/types/service/group.ts (1)
GroupIdPayload(163-165)src/api/index.ts (1)
API(22-22)src/lib/query-key/query-key-group/index.ts (1)
groupKeys(1-10)
src/components/pages/meetup/meetup-descriptions/description-sections/description-setting/index.tsx (3)
src/types/service/group.ts (1)
GetGroupDetailsResponse(125-161)src/components/pages/meetup/meetup-descriptions/description-sections/index.ts (1)
DescriptionSetting(4-4)src/lib/formatDateTime.ts (1)
formatDateTime(33-54)
src/components/pages/meetup/meetup-buttons/index.tsx (2)
src/types/service/group.ts (1)
GetGroupDetailsResponse(125-161)src/components/pages/meetup/meetup-modal/index.tsx (1)
MeetupModal(16-60)
src/components/pages/post-meetup/fields/date-field/index.tsx (2)
src/components/ui/modal/index.tsx (1)
useModal(18-22)src/lib/formatDateTime.ts (1)
formatDateTime(33-54)
🔇 Additional comments (17)
next.config.ts (1)
13-13: LGTM!S3 버킷 hostname 변경이 적절합니다. 새로운 이미지 호스팅 소스와 일치합니다.
src/components/pages/meetup/meetup-banner-images/index.tsx (1)
20-20: LGTM!
Boolean()변환을 통한 명시적 타입 처리가 좋습니다.src/lib/formatDateTime.ts (3)
10-10: UTC 가정에 대한 주의가 필요합니다.
'Z'접미사를 자동으로 추가하는 방식은 입력 문자열이 항상 UTC라고 가정합니다. 만약 로컬 타임존 문자열이 전달되면 잘못된 시간 차이가 계산될 수 있습니다. 백엔드에서 항상 UTC ISO 문자열을 반환하는지 확인이 필요합니다.
13-23: LGTM!
Math.ceil에서Math.floor로 변경하여 경과 시간만 표시하도록 수정한 점, 그리고diffPerDay계산을/30에서/24로 올바르게 수정한 점이 좋습니다.
46-53: LGTM!커스텀 포맷 지원이 잘 구현되었습니다.
yyyy가yy보다 먼저 처리되어 이중 치환 문제가 방지됩니다.src/components/pages/meetup/meetup-descriptions/description-sections/description-setting/index.tsx (2)
5-9: LGTM!
GetGroupDetailsResponse에서Pick을 사용하여 타입을 명확하게 정의한 점이 좋습니다. Props 구조가 일관성 있게 개선되었습니다.
19-19: LGTM!
formatDateTime유틸리티를 활용한 시간 포맷팅이 적절합니다.src/api/core/index.ts (1)
62-67: 클라이언트 측 404 처리가 누락되었습니다.서버 측에서는
notFound()를 호출하지만, 클라이언트 측에서는 404 에러가 별도 처리 없이throw errorResponse로 전달됩니다. 의도된 동작인지 확인이 필요합니다.클라이언트에서도 404 페이지로 이동해야 한다면:
if (status === 404) { if (isServer) { const { notFound } = await import('next/navigation'); notFound(); + } else { + window.location.href = '/404'; + return; } }src/types/service/group.ts (1)
83-87: 타입 정의가 명확하고 일관성 있게 개선되었습니다.PreUploadGroupImageResponse의 images 타입이 명시적인 배열 형태로 변경되었고, GroupIdPayload 인터페이스 추가로 그룹 ID 관련 페이로드의 타입 안정성이 향상되었습니다.
Also applies to: 122-122, 163-165
src/hooks/use-group/use-group-get-details/index.ts (1)
5-7: 타입 변경이 올바르게 적용되었습니다.GetGroupDetailsPayload에서 GroupIdPayload로의 타입 변경이 일관성 있게 적용되었습니다.
src/components/pages/meetup/meetup-descriptions/description-sections/index.ts (1)
1-1: 컴포넌트 export 변경이 올바르게 적용되었습니다.DescriptionContent에서 DescriptionDetail로의 export 변경이 리팩토링 의도에 맞게 적용되었습니다.
src/app/meetup/[groupId]/page.tsx (1)
28-30: LGTM!더미 데이터에서 실제 API 데이터로의 바인딩 변경이 올바르게 적용되었습니다. 각 컴포넌트의 Props 인터페이스와 일치합니다.
src/components/pages/meetup/meetup-descriptions/description-sections/description-title/index.tsx (2)
1-4: LGTM!
GetGroupDetailsResponse['title']인덱스 접근 타입 사용으로 타입 안전성이 향상되었습니다.
10-10:line-clamp제거로 인한 긴 제목 오버플로우 가능성 확인 필요.
line-clamp-2가 제거되고break-keep만 적용되었습니다. 매우 긴 제목의 경우 레이아웃을 벗어날 수 있으니, 최대 제목 길이가 API에서 제한되는지 확인해 주세요.src/components/pages/meetup/meetup-descriptions/description-sections/description-detail/index.tsx (1)
1-13: LGTM!새
DescriptionDetail컴포넌트가 깔끔하게 구현되었습니다. 타입 안전성과 한글 텍스트를 위한break-keep스타일링이 적절합니다.src/components/pages/meetup/meetup-descriptions/index.tsx (1)
11-47: 타입 안전성과 컴포넌트 구조가 잘 설계되었습니다.
Pick<GetGroupDetailsResponse, ...>를 사용한 타입 정의는 API 응답 타입과의 일관성을 유지하면서 필요한 필드만 명시적으로 요구합니다. 자식 컴포넌트들로의 props 매핑도 명확합니다.src/components/pages/meetup/meetup-buttons/index.tsx (1)
59-65: 이미 참여한 사용자의 채팅 입장 버튼 비활성화 로직 확인 필요이미 모임에 참여한 사용자(
isJoined === true)가 채팅에 입장하는 버튼인데,participantCount >= maxParticipants조건으로 비활성화됩니다. 모임이 꽉 찬 후에도 기존 참여자는 채팅에 들어갈 수 있어야 하지 않나요?
| // 나중에 회원 프로필, 모임 디폴트 시안 나오면 각각 '/public/default-images/' 경로에서 지정 | ||
| export const DEFAULT_PROFILE_IMAGE = | ||
| 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'; | ||
|
|
||
| export const DEFAULT_GROUP_IMAGE = | ||
| 'https://images.unsplash.com/photo-1705599359461-f99dc9e80efa?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'; |
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.
외부 URL 의존성에 대한 리스크를 추적하세요.
현재 Unsplash 외부 URL을 사용하고 있어, 해당 이미지가 삭제되거나 변경될 경우 문제가 발생할 수 있습니다. 주석에서 언급한 대로 로컬 이미지로 교체하는 작업을 이슈로 추적하는 것을 권장합니다.
이 작업을 추적하기 위한 이슈를 생성해 드릴까요?
🤖 Prompt for AI Agents
In constants/default-images.ts lines 1-6, the file uses external Unsplash URLs
which creates a runtime dependency risk if those images are removed/changed;
create an issue to replace these with local assets under /public/default-images
and update the constants to point to those local paths (e.g.,
/public/default-images/default-profile.png and
/public/default-images/default-group.png), and add a fallback strategy (serve a
bundled placeholder or log a warning) if local files are missing; include the
issue description, acceptance criteria, and assign to the frontend/assets owner
so the images are added and the constants updated.
| if (status === 401) { | ||
| if (isServer) { | ||
| const { redirect } = await import('next/navigation'); | ||
| redirect('/login'); | ||
| } else { | ||
| if (window.location.pathname === '/login') { | ||
| throw errorResponse; | ||
| } | ||
| const currentPath = window.location.pathname + window.location.search; | ||
| window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`; | ||
| } | ||
| } |
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.
클라이언트 측 401 처리 시 코드 흐름 문제가 있습니다.
window.location.href 할당 후에도 코드가 계속 실행되어 line 69의 throw errorResponse가 실행됩니다. 리다이렉트가 비동기적으로 발생하기 때문에, 불필요한 에러가 throw될 수 있습니다.
} else {
if (window.location.pathname === '/login') {
throw errorResponse;
}
const currentPath = window.location.pathname + window.location.search;
window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`;
+ return; // 리다이렉트 후 추가 실행 방지
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (status === 401) { | |
| if (isServer) { | |
| const { redirect } = await import('next/navigation'); | |
| redirect('/login'); | |
| } else { | |
| if (window.location.pathname === '/login') { | |
| throw errorResponse; | |
| } | |
| const currentPath = window.location.pathname + window.location.search; | |
| window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`; | |
| } | |
| } | |
| if (status === 401) { | |
| if (isServer) { | |
| const { redirect } = await import('next/navigation'); | |
| redirect('/login'); | |
| } else { | |
| if (window.location.pathname === '/login') { | |
| throw errorResponse; | |
| } | |
| const currentPath = window.location.pathname + window.location.search; | |
| window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`; | |
| return; // 리다이렉트 후 추가 실행 방지 | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/api/core/index.ts around lines 50 to 61, the client-side 401 branch
assigns window.location.href but execution continues and later reaches the throw
at line 69; stop further execution after initiating the redirect by immediately
returning (or using window.location.replace and then returning) right after
setting window.location.href so the subsequent throw/error logic is not executed
on the client.
| useEffect(() => { | ||
| const sessionId = Number(Cookies.get('userId')); | ||
| // eslint-disable-next-line react-hooks/set-state-in-effect | ||
| setIsHost(sessionId === createdBy.userId); | ||
| }, [createdBy]); |
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.
isHost 상태 초기화 및 쿠키 파싱 관련 문제가 있습니다.
Cookies.get('userId')가undefined를 반환할 수 있으며,Number(undefined)는NaN이 됩니다.isHost가null인 초기 렌더링 시점에 Line 54의isHost ? 'delete' : 'cancel'이'cancel'로 평가됩니다.
useEffect(() => {
- const sessionId = Number(Cookies.get('userId'));
- // eslint-disable-next-line react-hooks/set-state-in-effect
- setIsHost(sessionId === createdBy.userId);
+ const userIdCookie = Cookies.get('userId');
+ if (userIdCookie) {
+ const sessionId = Number(userIdCookie);
+ setIsHost(sessionId === createdBy.userId);
+ } else {
+ setIsHost(false);
+ }
}, [createdBy]);🤖 Prompt for AI Agents
In src/components/pages/meetup/meetup-buttons/index.tsx around lines 40-44, the
effect blindly does Number(Cookies.get('userId')) which can produce NaN and you
currently initialize isHost to null causing the early render to treat the
ternary as 'cancel'; fix by parsing the cookie safely (e.g. read cookie string,
use parseInt and check isNaN) and only setIsHost when the parsed sessionId is a
valid number, and also initialize isHost to false (or compute synchronously from
the cookie) so the initial render doesn't treat null as falsy; ensure the effect
compares the numeric sessionId with createdBy.userId (or createdBy.userId as
Number) and update the dependency list appropriately.
| <p className='text-text-md-semibold text-gray-800'>{name}</p> | ||
| <p className='text-text-xs-regular text-gray-600'>{bio}</p> | ||
| <p className='text-text-md-semibold text-gray-800'>{nickName}</p> | ||
| <p className='text-text-xs-regular text-gray-600'>some dummy bio text</p> |
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.
하드코딩된 더미 bio 텍스트.
"some dummy bio text"가 하드코딩되어 있습니다. API 응답에 bio 필드가 없다면, 해당 UI를 제거하거나 TODO 주석을 추가하여 추후 작업을 명시해 주세요.
🤖 Prompt for AI Agents
In
src/components/pages/meetup/meetup-descriptions/description-sections/description-profile/index.tsx
around line 28, there is a hardcoded bio string ("some dummy bio text"); remove
the hardcoded text and replace it with conditional rendering of the actual bio
prop (e.g., render user.bio when present), otherwise render nothing or a small
placeholder; if you do not implement the API hookup now, replace the string with
an explicit TODO comment indicating to wire up the bio field and remove the
placeholder later.
| progress: { maxParticipants, participantCount }, | ||
| createdAt, | ||
| }: Props) => { | ||
| const progressRate = Math.ceil((participantCount / maxParticipants) * 100); |
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.
maxParticipants가 0일 때 division by zero 오류 가능성이 있습니다.
maxParticipants가 0인 경우 NaN 또는 Infinity가 발생할 수 있습니다. 방어적 처리가 필요합니다.
- const progressRate = Math.ceil((participantCount / maxParticipants) * 100);
+ const progressRate = maxParticipants > 0
+ ? Math.ceil((participantCount / maxParticipants) * 100)
+ : 0;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const progressRate = Math.ceil((participantCount / maxParticipants) * 100); | |
| const progressRate = maxParticipants > 0 | |
| ? Math.ceil((participantCount / maxParticipants) * 100) | |
| : 0; |
🤖 Prompt for AI Agents
In
src/components/pages/meetup/meetup-descriptions/description-sections/description-progress/index.tsx
around line 16, the calculation const progressRate = Math.ceil((participantCount
/ maxParticipants) * 100); can divide by zero when maxParticipants is 0; change
it to first guard against maxParticipants <= 0 (treat as no capacity) and
compute a safe progressRate: if maxParticipants <= 0 set progressRate to 0 (or
an appropriate sentinel), otherwise compute the percentage and clamp the result
to the 0–100 range (also ensure participantCount is non-negative) before
applying Math.ceil so you never produce NaN or Infinity.
| const defaultProfileImageUrl = | ||
| 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'; | ||
|
|
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 | 🟠 Major
기본 프로필 이미지 상수 중복.
DEFAULT_PROFILE_IMAGE 상수가 constants/default-images.ts에 이미 정의되어 있습니다. 중복 코드를 제거하고 상수를 import하여 사용해 주세요.
+import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images';
import { GetGroupDetailsResponse } from '@/types/service/group';
// ...
- const defaultProfileImageUrl =
- 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D';
-
// ...
- src={profileImage ?? defaultProfileImageUrl}
+ src={profileImage ?? DEFAULT_PROFILE_IMAGE}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const defaultProfileImageUrl = | |
| 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'; |
🤖 Prompt for AI Agents
In src/components/pages/meetup/meetup-members/index.tsx around lines 25 to 27,
remove the locally defined defaultProfileImageUrl constant and instead import
DEFAULT_PROFILE_IMAGE from constants/default-images.ts; update the file's import
section to add: import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images'
(adjust relative path if needed), replace all uses of defaultProfileImageUrl
with DEFAULT_PROFILE_IMAGE, and delete the now-unused local constant.
| const hasValue = Boolean(field.state.value); | ||
| const formattedDate = formatDateTime(field.state.value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
field.state.value가 비어있을 때 Invalid Date 발생 가능성이 있습니다.
formatDateTime이 hasValue 체크 전에 무조건 호출됩니다. field.state.value가 빈 문자열이나 undefined일 경우 new Date('')는 Invalid Date를 생성하여 "NaN. NaN. NaN - NaN:NaN" 형태의 문자열이 반환됩니다.
const hasValue = Boolean(field.state.value);
- const formattedDate = formatDateTime(field.state.value);
+ const formattedDate = hasValue ? formatDateTime(field.state.value) : '';🤖 Prompt for AI Agents
In src/components/pages/post-meetup/fields/date-field/index.tsx around lines
19-20, formatDateTime is called unconditionally on field.state.value which can
be empty/undefined and produce "Invalid Date"; guard the call by checking
hasValue first (e.g., compute formattedDate only when hasValue is true) or
update formatDateTime to return an empty string/null for falsy inputs, and use
that guarded value in the render so you never call new Date('') or render NaN
output.
| export const useDeleteGroup = (payload: GroupIdPayload, callback: () => void) => { | ||
| const query = useMutation({ | ||
| mutationFn: () => API.groupService.deleteGroup(payload), | ||
| onSuccess: async () => { | ||
| console.log('모임 삭제 성공.'); | ||
| callback(); | ||
| }, | ||
| onError: () => { | ||
| console.log('모임 삭제 실패.'); | ||
| }, | ||
| }); | ||
| return query; |
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.
모임 삭제 후 쿼리 무효화가 누락되었습니다.
useAttendGroup과 useCancelGroup은 성공 시 queryClient.invalidateQueries를 호출하여 상세 정보를 갱신하지만, useDeleteGroup은 쿼리 무효화를 수행하지 않습니다. 모임 삭제 후에는 해당 모임의 상세 쿼리를 무효화하거나 사용자를 다른 페이지로 리다이렉트해야 합니다.
-import { useMutation } from '@tanstack/react-query';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
import { API } from '@/api';
+import { groupKeys } from '@/lib/query-key/query-key-group';
import { GroupIdPayload } from '@/types/service/group';
export const useDeleteGroup = (payload: GroupIdPayload, callback: () => void) => {
+ const queryClient = useQueryClient();
+
const query = useMutation({
mutationFn: () => API.groupService.deleteGroup(payload),
onSuccess: async () => {
+ await queryClient.invalidateQueries({ queryKey: groupKeys.detail(payload.groupId) });
console.log('모임 삭제 성공.');
callback();
},🤖 Prompt for AI Agents
In src/hooks/use-group/use-group-delete/index.ts around lines 6 to 17, the hook
is missing query invalidation after a successful delete; update the onSuccess
handler to obtain the react-query QueryClient (via useQueryClient), call
queryClient.invalidateQueries for the group's detail/list queries (e.g. the key
used for group detail like ['group', payload.groupId] and any list queries),
then call the existing callback; alternatively, if you want to redirect instead
of invalidating, invoke a navigation callback (e.g. router.push) in onSuccess to
send the user away from the deleted group's page.
claudia99503
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏🏻
📝 변경 사항
[작업]
[수정]
formatDateTime() 수정 사항
${shortYear}. ${month}. ${day} - ${hours}:${minutes}포맷에서(customFormat?: string | null) 옵셔널 파라미터 추가로 재사용성 효율 높임.
customFormat에 정의할 "yy-MM-dd"와 같은 형식은 date-fns 또는 date와 관련한 라이브러리의 형식을 따름.
formatTimeAgo() 수정 사항
5252.. 오마에.. 마지카요.. 하루는 24시간 이라구 wwwww
${isoString}Z) 변경하지만, 서버에서 받는 createdAt는 "Z"가 빠진 상태로 오기 때문에 Locale 기반으로 시간을 잡아버림.
이러한 이유로 모임 생성 1분 전이라 할 지라도 UTC 타임과 Locale 타임의 차이로 수시간의 오차가 생김.
[wip]
🔗 관련 이슈
Closes #
🧪 테스트 방법
📸 스크린샷 (선택)
📋 체크리스트
💬 추가 코멘트
CodeRabbit Review는 자동으로 실행되지 않습니다.
Review를 실행하려면 comment에 아래와 같이 작성해주세요
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선사항
✏️ Tip: You can customize this high-level summary in your review settings.