Conversation
- 약속회고 상세 페이지에 댓글 작성/삭제 기능 추가. - 무한 스크롤로 댓글 목록 조회하며, 약속장 및 댓글 작성자만 삭제 가능.
Walkthrough회의 회고 모듈을 재구성하고 미팅 회고 관련 기능을 meeting 서브모듈로 집중했습니다. 미팅 회고의 댓글 CRUD(목록 페이징·무한스크롤, 생성, 삭제), 관련 훅·API·타입·모의데이터, 날짜 포맷 유틸, UI 변화(Textarea/Input 카운터 등), 페이지 경로 재배치가 추가되었습니다. Changes
Sequence DiagramsequenceDiagram
participant User as 사용자
participant UI as RetrospectiveComments
participant Query as useRetrospectiveComments
participant API as retrospectives.api
participant Server as 서버
User->>UI: 페이지 진입 (meetingId)
UI->>Query: useRetrospectiveComments(meetingId)
Query->>API: getComments({meetingId, pageSize, cursor})
API->>Server: GET /meetings/:id/retrospectives/comments
Server-->>API: {items, hasNext, nextCursor, totalCount}
API-->>Query: 응답 반환(캐시)
Query-->>UI: 댓글 데이터 렌더링
User->>UI: 새 댓글 입력 후 제출
UI->>API: createComment({meetingId, content})
API->>Server: POST /meetings/:id/retrospectives/comments
Server-->>API: createdComment
API->>Query: invalidate commentsList(meetingId)
Query->>API: (재요청) getComments
API-->>Query: 갱신된 댓글 목록
Query-->>UI: UI 갱신
User->>UI: 댓글 삭제 요청 (권한 확인)
UI->>API: deleteComment({meetingId, commentId})
API->>Server: DELETE /meetings/:id/retrospectives/:commentId
Server-->>API: OK
API->>Query: invalidate commentsList(meetingId)
Query->>API: (재요청) getComments
API-->>Query: 갱신된 댓글 목록
Query-->>UI: UI 갱신
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
주요 검토 포인트문제 → 영향 → 대안
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (1)
src/features/retrospectives/meeting/retrospectives.api.ts (1)
210-236:USE_MOCK환경에서 댓글 작성/삭제만 실제 API를 호출합니다문제: Line 210-236은
USE_MOCK분기가 없어 목환경에서도 네트워크 POST/DELETE를 바로 호출합니다.
영향: 로컬/스토리북/목테스트 환경에서 댓글 기능이 실패하거나 테스트가 불안정해집니다.
대안:retrospectives.mock.ts에 생성/삭제 헬퍼를 추가하고 읽기 API와 동일하게 mock 분기를 맞춰주세요.수정 예시
export const createComment = async ( params: CreateCommentParams ): Promise<CreateCommentResponse> => { const { meetingId, comment } = params + if (USE_MOCK) { + await new Promise((resolve) => setTimeout(resolve, 200)) + return createMockComment(meetingId, comment) + } return api.post<CreateCommentResponse>(RETROSPECTIVES_ENDPOINTS.COMMENTS(meetingId), { comment, }) } export const deleteComment = async (params: DeleteCommentParams): Promise<void> => { const { meetingId, commentId } = params + if (USE_MOCK) { + await new Promise((resolve) => setTimeout(resolve, 200)) + deleteMockComment(meetingId, commentId) + return + } return api.delete<void>(RETROSPECTIVES_ENDPOINTS.COMMENT_DELETE(meetingId, commentId)) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/retrospectives/meeting/retrospectives.api.ts` around lines 210 - 236, The createComment and deleteComment functions currently always call the real API; add the same USE_MOCK branching used by the read APIs so mocks are used in mock environments: inside createComment (function name: createComment) and deleteComment (function name: deleteComment) check the USE_MOCK flag and, when true, delegate to the corresponding mock helpers you should add to retrospectives.mock.ts (e.g., createCommentMock/deleteCommentMock or similar), otherwise call api.post to RETROSPECTIVES_ENDPOINTS.COMMENTS(meetingId) and api.delete to RETROSPECTIVES_ENDPOINTS.COMMENT_DELETE(meetingId, commentId) as before; ensure signatures and returned Promise types match CreateCommentResponse and void so callers are unaffected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/features/retrospectives/meeting/components/RetrospectiveComments.tsx`:
- Around line 49-55: The current render uses "isLoading || !commentsData" so
when fetching fails the skeleton keeps showing; update the RetrospectiveComments
component to branch on "isError" from useRetrospectiveComments (in addition to
isLoading, data, etc.) and render an error state that shows an error message and
a retry control that calls "refetch"; specifically, locate the
useRetrospectiveComments call and the rendering blocks that reference
commentsData/isLoading and replace the single loading-or-empty branch with three
branches: isLoading -> skeleton, isError -> error UI with a button wired to
refetch, otherwise -> render commentsData pages; apply the same change to the
similar block referenced around the 154-156 area.
- Around line 115-117: The canDelete function treats currentUserId falsy values
(e.g., 0) as not logged in; change the guard so only null/undefined are
rejected: in the canDelete function replace the falsy check with an explicit
null/undefined check (e.g., currentUserId == null or currentUserId === null ||
currentUserId === undefined) so users with ID 0 can still be recognized, keeping
the rest of the logic comparing currentUserId to meetingLeaderId and
commentUserId unchanged.
In
`@src/features/retrospectives/meeting/components/RetrospectiveCommentsSkeleton.tsx`:
- Around line 21-22: RetrospectiveCommentsSkeleton에서 사용 중인
[...Array(count).keys()]는 count가 음수거나 소수일 때 런타임 예외를 발생시킬 수 있으니, 렌더링 전에 count 변수를
0 이상의 정수로 보정하세요 (예: undefined/null을 0으로, 음수는 0으로, 소수는 내림). 즉
RetrospectiveCommentsSkeleton 컴포넌트 내에서 count를 정규화한 로컬 변수(예: normalizedCount)로
치환한 뒤 [...Array(normalizedCount).keys()].map(...)를 사용하거나 Prop 기본값/타입 체크를 추가해
안전하게 배열을 생성하도록 수정하세요.
In `@src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts`:
- Around line 26-31: The isValid check currently uses !Number.isNaN(meetingId)
which allows Infinity; change the validation used for the enabled flag to use
Number.isFinite so Infinity is rejected. Update the isValid definition
(referencing meetingId and isValid) used by useQuery
(retrospectiveQueryKeys.detail / getMeetingRetrospectiveDetail / enabled) to use
Number.isFinite(meetingId) && meetingId > 0 so the hook doesn't enable queries
for Infinity or other non-finite values.
In `@src/features/retrospectives/meeting/lib/dateFormatters.ts`:
- Line 39: The example in the doc comment for formatToDateWithDay is showing the
wrong weekday; update the example string from '2026.01.15(월)' to '2026.01.15(목)'
so the sample matches the actual date for '2026-01-15T09:30:00Z'—locate the
comment near the formatToDateWithDay function in dateFormatters.ts and change
only the example text.
In `@src/features/retrospectives/meeting/retrospectives.endpoints.ts`:
- Around line 26-27: The COMMENT_DELETE endpoint currently builds
`${API_PATHS.MEETINGS}/${meetingId}/retrospectives/${commentId}` which
mismatches the COMMENTS resource namespace; update COMMENT_DELETE to use the
same namespace as COMMENTS by changing the path to
`${API_PATHS.MEETINGS}/${meetingId}/retrospectives/comments/${commentId}` so
deletes target retrospectives/comments/{commentId} and align with the COMMENTS
route.
In `@src/features/retrospectives/meeting/retrospectives.mock.ts`:
- Around line 269-271: 토픽 제목 '선과 악, 현실과 연결고리'에 대한 topicDescription 값이 문장이 잘려있고
맥락과 맞지 않으므로 해당 객체의 topicDescription 문자열을 토픽 의미에 맞는 완결된 문장으로 교체하세요; 대상은
retrospectives.mock.ts에서 topicTitle이 '선과 악, 현실과 연결고리'인 항목의 topicDescription 필드이며
예를 들어 “선과 악의 경계가 현실에서 어떻게 연결되고 충돌하는지를 성찰하는 내용”처럼 토픽의 주제를 요약하는 자연스러운 한 문장으로 바꿔 주면
됩니다.
In `@src/pages/Retrospectives/meeting/MeetingRetrospectiveDetailPage.tsx`:
- Line 66: The Tabs component is given a defaultValue built from
data.topics[0]?.topicId which yields "topic-undefined" when topics is empty;
change the render flow in MeetingRetrospectiveDetailPage to check data.topics
(e.g., if (!data.topics || data.topics.length === 0)) and render a dedicated
empty-state UI instead of rendering <Tabs
defaultValue={`topic-${data.topics[0]?.topicId}`}>; ensure any code referencing
topicId (data.topics[0].topicId) is only executed after confirming topics has at
least one element so defaultValue is always valid.
- Around line 24-25: The current parsing uses Number.isFinite and a separate ===
0 guard which allows negative/decimal IDs through; update the validation for
parsedGatheringId and parsedMeetingId so gatheringId and meetingId are set only
if parsed values are positive integers (e.g. Number.isInteger(parsedX) &&
parsedX > 0), otherwise treat them as invalid (null/undefined or trigger an
early return/redirect to a 404 or error state) and ensure the subsequent code
path that checks for missing data (the !data spinner branch) is not left waiting
for requests with invalid IDs; adjust any checks that currently rely on === 0
accordingly.
In `@src/shared/ui/Textarea.tsx`:
- Around line 112-115: In the Textarea component (Textarea.tsx) the class list
currently always includes 'custom-scroll' causing comment-only scroll styling to
leak to other formats; update the className array (the expression that builds
the classes for the textarea element) so that 'custom-scroll' is only added when
format === 'comment' (i.e., change the unconditional 'custom-scroll' entry to a
conditional one alongside the existing format === 'comment' check), ensuring
disabled and other class conditions remain unchanged.
- Around line 60-97: The comment-mode auto-resize is broken because the
component mixes the generic prop height (height=180) with a fixed maxHeight
(maxHeight: '128px') and still compares scrollHeight against height; fix this by
introducing a baseHeight used when format === 'comment' (e.g., const baseHeight
= format === 'comment' ? 48 : height), update handleInput to reset and compare
against baseHeight (reset target.style.height = `${baseHeight}px` and compare
scrollHeight > baseHeight), and use baseHeight in computedStyle for the comment
branch (height: `${baseHeight}px`, keep maxHeight: '128px'); update references
in handleInput, computedStyle, and any places reading height so comment mode
relies on baseHeight not the generic height prop.
---
Nitpick comments:
In `@src/features/retrospectives/meeting/retrospectives.api.ts`:
- Around line 210-236: The createComment and deleteComment functions currently
always call the real API; add the same USE_MOCK branching used by the read APIs
so mocks are used in mock environments: inside createComment (function name:
createComment) and deleteComment (function name: deleteComment) check the
USE_MOCK flag and, when true, delegate to the corresponding mock helpers you
should add to retrospectives.mock.ts (e.g., createCommentMock/deleteCommentMock
or similar), otherwise call api.post to
RETROSPECTIVES_ENDPOINTS.COMMENTS(meetingId) and api.delete to
RETROSPECTIVES_ENDPOINTS.COMMENT_DELETE(meetingId, commentId) as before; ensure
signatures and returned Promise types match CreateCommentResponse and void so
callers are unaffected.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (39)
src/features/retrospectives/index.tssrc/features/retrospectives/meeting/components/AiGradientIcon.tsxsrc/features/retrospectives/meeting/components/AiLoadingOverlay.tsxsrc/features/retrospectives/meeting/components/AiSummaryToast.tsxsrc/features/retrospectives/meeting/components/RetrospectiveCardButtons.tsxsrc/features/retrospectives/meeting/components/RetrospectiveComments.tsxsrc/features/retrospectives/meeting/components/RetrospectiveCommentsSkeleton.tsxsrc/features/retrospectives/meeting/components/RetrospectiveSummarySkeleton.tsxsrc/features/retrospectives/meeting/components/index.tssrc/features/retrospectives/meeting/hooks/index.tssrc/features/retrospectives/meeting/hooks/retrospectiveQueryKeys.tssrc/features/retrospectives/meeting/hooks/useCollectedAnswers.tssrc/features/retrospectives/meeting/hooks/useCreateRetrospectiveComment.tssrc/features/retrospectives/meeting/hooks/useCreateSttJob.tssrc/features/retrospectives/meeting/hooks/useDeleteRetrospectiveComment.tssrc/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.tssrc/features/retrospectives/meeting/hooks/usePublishSummary.tssrc/features/retrospectives/meeting/hooks/useRetrospectiveComments.tssrc/features/retrospectives/meeting/hooks/useSummary.tssrc/features/retrospectives/meeting/hooks/useUpdateSummary.tssrc/features/retrospectives/meeting/index.tssrc/features/retrospectives/meeting/lib/dateFormatters.tssrc/features/retrospectives/meeting/lib/index.tssrc/features/retrospectives/meeting/retrospectives.api.tssrc/features/retrospectives/meeting/retrospectives.endpoints.tssrc/features/retrospectives/meeting/retrospectives.mock.tssrc/features/retrospectives/meeting/retrospectives.types.tssrc/features/retrospectives/retrospectives.mock.tssrc/pages/Meetings/MeetingDetailPage.tsxsrc/pages/Retrospectives/MeetingRetrospectiveDetailPage.tsxsrc/pages/Retrospectives/index.tssrc/pages/Retrospectives/meeting/MeetingRetrospectiveCreatePage.tsxsrc/pages/Retrospectives/meeting/MeetingRetrospectiveDetailPage.tsxsrc/pages/Retrospectives/meeting/MeetingRetrospectivePage.tsxsrc/pages/Retrospectives/meeting/index.tssrc/shared/constants/pagination.tssrc/shared/ui/Input.tsxsrc/shared/ui/Tabs.tsxsrc/shared/ui/Textarea.tsx
💤 Files with no reviewable changes (3)
- src/features/retrospectives/retrospectives.mock.ts
- src/pages/Retrospectives/MeetingRetrospectiveDetailPage.tsx
- src/features/retrospectives/index.ts
src/features/retrospectives/meeting/components/RetrospectiveComments.tsx
Show resolved
Hide resolved
src/features/retrospectives/meeting/components/RetrospectiveComments.tsx
Show resolved
Hide resolved
src/features/retrospectives/meeting/components/RetrospectiveCommentsSkeleton.tsx
Outdated
Show resolved
Hide resolved
src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts
Outdated
Show resolved
Hide resolved
- 댓글 조회 실패 시 에러 토스트 및 안내 메시지 표시. - currentUserId null 체크로 삭제 권한 확인 로직 개선. - 스켈레톤 count 음수값 방어 처리. - Textarea format별 height 기본값 적용.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts (1)
29-29:⚠️ Potential issue | 🟡 Minor
enabled조건이 비정상 숫자(Infinity)를 통과시킵니다.문제: Line 29에서
meetingId > 0만 검사해Infinity(및 정수가 아닌 양수)를 허용합니다.
영향: 잘못된 ID로 상세 조회 요청이 나가 4xx/불필요 재시도 등 UX 저하를 유발할 수 있습니다.
대안:Number.isFinite(+ 필요 시Number.isInteger)를 함께 사용해enabled를 제한하세요.🔧 제안 수정
- enabled: meetingId > 0, + enabled: Number.isFinite(meetingId) && Number.isInteger(meetingId) && meetingId > 0,#!/bin/bash # enabled 조건이 유한/정수 검증을 포함하는지 확인 rg -n -C2 "enabled:\s*" src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts rg -n "Number\.isFinite\(meetingId\)|Number\.isInteger\(meetingId\)" src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.tsAs per coding guidelines, "queryKey 안정성, enabled 조건 ... 중점적으로 봐줘."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts` at line 29, The enabled check currently only uses "meetingId > 0" and allows non-finite values like Infinity; update the enabled predicate in useMeetingRetrospectiveDetail to ensure meetingId is a finite (and optionally integer) positive value by combining Number.isFinite(meetingId) (and Number.isInteger(meetingId) if you require integers) with the > 0 check so the hook only fires for valid IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts`:
- Line 29: The enabled check currently only uses "meetingId > 0" and allows
non-finite values like Infinity; update the enabled predicate in
useMeetingRetrospectiveDetail to ensure meetingId is a finite (and optionally
integer) positive value by combining Number.isFinite(meetingId) (and
Number.isInteger(meetingId) if you require integers) with the > 0 check so the
hook only fires for valid IDs.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/features/retrospectives/meeting/components/RetrospectiveComments.tsxsrc/features/retrospectives/meeting/components/RetrospectiveCommentsSkeleton.tsxsrc/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.tssrc/shared/ui/Textarea.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- src/features/retrospectives/meeting/components/RetrospectiveComments.tsx
- src/features/retrospectives/meeting/components/RetrospectiveCommentsSkeleton.tsx
- src/shared/ui/Textarea.tsx
약속 회고 파일 구조를 meeting 폴더로 재구성하면서 develop 브랜치의 변경사항 병합: - 개인 회고 기능 추가 (personal 폴더) - Summary 관련 컴포넌트 및 함수 추가 (SummaryInfoBanner, TopicSummaryCard) - 파일 구조 재정리: retrospectives를 meeting/personal로 분리 - MeetingRetrospectiveResultPage를 meeting 폴더로 이동 - AiGradientIcon 삭제 (develop에서 제거됨) - 모든 import 경로를 meeting 구조에 맞게 수정 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/features/retrospectives/meeting/retrospectives.api.ts`:
- Around line 256-264: createComment and deleteComment currently always call the
real API; add the same USE_MOCK branch used by getComments so mock flows are
used in tests. Specifically, update createComment and deleteComment to check
USE_MOCK and, when true, invoke the corresponding mock handlers (e.g.,
mockCreateComment / mockDeleteComment or existing mock logic used by
getComments) and return the same CreateCommentResponse/DeleteCommentResponse
shape instead of calling api.post/api.delete against
RETROSPECTIVES_ENDPOINTS.COMMENTS (and the delete endpoint). Ensure the mock
path mirrors getComments' behavior and only the real api.* calls run when
USE_MOCK is false.
In `@src/features/retrospectives/meeting/retrospectives.mock.ts`:
- Around line 421-423: The function getMockMeetingRetrospectiveDetail currently
ignores meetingId and returns the same object reference; change its signature to
accept a meetingId parameter, create a deep copy via
structuredClone(mockMeetingRetrospectiveDetail), set the copied object's
meetingId to the provided meetingId, and return the cloned object so callers
receive distinct instances; reference getMockMeetingRetrospectiveDetail and the
mockMeetingRetrospectiveDetail constant when making the change.
ℹ️ Review info
Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 779ea60b-7810-4dae-9d4a-47865113a9fb
📒 Files selected for processing (12)
src/features/retrospectives/index.tssrc/features/retrospectives/meeting/components/AiLoadingOverlay.tsxsrc/features/retrospectives/meeting/components/RetrospectiveCardButtons.tsxsrc/features/retrospectives/meeting/components/SummaryInfoBanner.tsxsrc/features/retrospectives/meeting/components/TopicSummaryCard.tsxsrc/features/retrospectives/meeting/components/index.tssrc/features/retrospectives/meeting/retrospectives.api.tssrc/features/retrospectives/meeting/retrospectives.mock.tssrc/pages/Retrospectives/index.tssrc/pages/Retrospectives/meeting/MeetingRetrospectiveCreatePage.tsxsrc/pages/Retrospectives/meeting/MeetingRetrospectiveResultPage.tsxsrc/pages/Retrospectives/meeting/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/pages/Retrospectives/index.ts
- src/pages/Retrospectives/meeting/index.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/features/retrospectives/meeting/retrospectives.api.ts`:
- Around line 232-235: The mock branch is dropping meetingId when calling mock
helpers, causing different meetings to share comment state; update the calls in
retrospectives.api.ts to pass the meetingId into getMockComments,
mockCreateComment, and mockDeleteComment, and update the mock helpers'
signatures accordingly (e.g., getMockComments(meetingId, pageSize?,
cursorCreatedAt?, cursorCommentId?), mockCreateComment(meetingId, comment),
mockDeleteComment(meetingId, commentId)) so mock state is keyed by meetingId and
comment operations affect only the intended meeting.
ℹ️ Review info
Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7e6d27df-be8a-49ff-aada-c70ee55d9d9f
📒 Files selected for processing (2)
src/features/retrospectives/meeting/retrospectives.api.tssrc/features/retrospectives/meeting/retrospectives.mock.ts
src/features/retrospectives/meeting/components/RetrospectiveComments.tsx
Outdated
Show resolved
Hide resolved
- window.confirm을 글로벌 모달로 변경 - Mock 함수에서 불필요한 meetingId 파라미터 제거 - Textarea 컴포넌트에서 사용하지 않는 ref 제거.
* [refactor] 사전의견 폴더 병합 (#92) * refactor: 사전의견 관련 폴더 병합 (#91) * fix: SubPageHeader 그림자 prop 추가 (#91) * refactor: 코드 포매팅 (#91) * fix: 빌드 에러 수정 (#91) * fix: 개인 회고 nullable 필드 처리 및 목 모드 조기 반환 추가 (#91) * chore: 개인회고 관련 파일 삭제 (#91) * feat: 약속 회고 API 레이어 구축 (#94) (#95) * feat: 약속 회고 API 레이어 구축 (#94) STT Job 생성, 요약 조회/수정/발행 API 함수 및 React Query 훅 구현. AbortController 기반 STT 요청 취소 지원, setQueryData 캐시 전략 적용. * style: prettier 포맷 수정 (#94) * refactor: 코드 리뷰 반영 (#94) - api 함수 api.* 헬퍼로 통일 - useCreateSttJob race condition 수정 - useCreateSttJob 언마운트 시 자동 취소 - KeyPointUpdateRequest 타입 alias로 변경 * feat: 에러 코드 추가 및 사전 의견 버튼 disabled 처리 (#97) (#98) 신규 에러 코드 5개(E000, M016, M017, R106, R108) 추가. MyMeetingListItem에 preOpinionTemplateConfirmed 필드 추가 후, 메인페이지 예정 약속의 사전 의견 작성 버튼 disabled 처리 반영. * [feat] 약속회고 생성 전 플로우 UI 및 기능 개발 (#100) * feat: 약속 완료 시 약속 상세 페이지 UI 변경(#87) * feat: 약속 회고 상세 페이지 추가 (#87) * feat:아코디언 컴포넌트 추가(#87) * design: 아코디언 컴포넌트css 수정(#87) * feat: 수집된 사전의견 조회 api 개발(#87) * feat: Dropzone 공통컴포넌트 생성 및 파일 업로드 기능구현(#87) * design: AI요약 버튼 디자인 수정(#87) * style: 프리티어(#87) * refactor: 파일 업로드 검증 강화 및 의존성 정리 (#87) Dropzone 파일 타입 검증 기능 추가 및 radix-ui 의존성 정리로 코드 품질 개선. - Dropzone 파일 타입 검증 로직 추가 - 파일 타입 불일치 시 onTypeRejected 콜백 호출 - alert를 toast로 변경하여 일관된 에러 처리 - radix-ui 제거 후 @radix-ui/react-accordion 추가 - Accordion, AlertIcon 컴포넌트 코드 정리 - Import/export 경로 정리 및 불필요한 mock export 제거 * refactor: 에러처리 개선 (#87) * design: 아코디언css 수정 (#87) * refactor: GetCollectedAnswersParams 배럴 추가(#87) * style: 주석제거(#87) * refactor: AlertIcon 색 커스텀 기능 추가(#87) * refactor: 회고 아이콘을 SVG 파일로 전환 (#87) - MeetingRetroIcon, PersonalRetroIcon 컴포넌트를 SVG 파일로 대체 - useCollectedAnswers 훅의 유효성 검사 강화 (정수 체크, pageSize 검증) - MeetingDetailPage 에러 처리 중복 실행 방지 * refactor: AlertIcon적용 (#87) * refactor: AlertIcon size prop추가(#87) * refactor: 주제 목록 에러 처리 개선 (#87) * fix: 버튼 프로퍼티수정(#87) * feat: 회고 상태 기반 라우트 분기 처리 추가 (#87) * feat: 목데이터에 회고상태 추가(#87) * refactor: params검증로직 페이지에서 처리(#87) * [feat] 개인회고 작성 및 조회 화면 구현 (#101) * feat: 개인 회고 작성 화면 구현 (#90) * feat: 개인 회고 전송 기능 구현 (#90) * feat: 개인회고 조회 화면 구현 (#90) * feat: 개인회고 삭제, 수정 구현 (#90) * chore 미사용 유틸리티 제거 (#90) * refactor: 코드 포매팅 (#90) * fix: 코드리뷰 반영 (#90) * fix: 코드 리뷰 반영 (#90) * refactor: 코드 포매팅 (#90) * refactor: 개인회고 폴더 정리 (#90) * fix: 코드 리뷰 반영 (#90) * feat: 약속 회고 결과 페이지 UI 및 기능 개발 (#93) (#102) * feat: 약속 회고 결과 페이지 UI 및 기능 개발 (#93) - AI 요약 결과 조회, 수정, 발행 기능 구현 - TopicSummaryCard: 구조화된 개별 input으로 주요포인트 편집 - FormPageHeader: children, onBack prop 추가 - 발행 성공 시 DetailPage로 이동 - AiLoadingOverlay SVG 직접 import 방식으로 변경 - summary 관련 목데이터 추가 * fix: 코드 리뷰 반영 (#93) - TopicSummaryCard 편집 모드 keyPoints map key를 인덱스에서 stable id로 교체 (EditableKeyPoint 타입 추가, crypto.randomUUID()로 id 생성) - mock abort 에러를 DOMException에서 Error('canceled')로 변경해 axios 취소 동작 일치 - 회고 요약 목데이터를 전역 단일 객체에서 meetingId별 Record로 리팩터링 - MeetingRetrospectiveResultPage에 isError 처리 및 다시 시도 버튼 추가 - window.history.replaceState를 navigate(location.pathname, replace)로 교체 - FormPageHeader props를 discriminated union으로 타입 강화 * style: prettier 포맷 수정 (#93) * fix: 코드 리뷰 반영 2차 (#93) - 상세 항목(details)에도 EditableDetail 타입으로 stable id 적용 (추가/삭제 시 input 포커스 혼동 방지) - handleSaveEdit에서 EditableDetail[] → string[]으로 직렬화 처리 - mock STT abort: aborted 즉시 감지, once 옵션으로 리스너 중복 방지, resolve 시 리스너 정리 - meetingId 유효성 검사 강화 (Number.isInteger && > 0) * fix: 코드 리뷰 반영 3차 (#93) - guard를 핸들러 정의 이전으로 이동해 gatheringId/meetingId non-null assertion 제거 - topic prop을 항상 SummaryTopic[]으로 유지해 union 타입 불일치 해결 - EditableDetail/EditableKeyPoint를 components/index.ts에서 함께 re-export * [fix] 책 상세 페이지, 내 책장 페이지 API 스펙 반영 (#104) * refactor: 감상 기록 액션 메뉴 분리 (#59) * refactor: Book/Keywords API 목데이터와 엔드포인트 분리 (#59) * design: 감상 기록 액션 메뉴 관련 z-index 조정 (#59) * feat: 별점 필터링에 0점 추가 (#59) * refactor: 내 책장 무한스크롤 시 useInfiniteScroll 활용 (#59) * feat: 내 책장에 등록된 책이 없을 경우 툴팁 추가 (#59) * feat: 기록 타임라인의 사전의견에 답변이 없는 주제 처리 추가 (#59) * refactor: sticky한 헤더에 대해 drop-shadow-bottom 및 useScrollCollapse 활용 (#59) * fix: 책 타임라인 api 반영 (#59) * fix: 책 리스트 조회 api 반영 (#59) * fix: 책 리스트 삭제 api 반영 및 디자인 수정 (#59) * fix: 도서 API 스펙 변경에 따른 타입 및 컴포넌트 업데이트 (#59) * fix: 약속 회고 아이템에서 미연동 액션 메뉴 제거 (#59) * feat: 도서 읽기 상태 토글에 낙관적 업데이트 적용 (#59) * refactor: 코드 포매팅 (#59) * fix: 메인페이지 ReadingBooksSection API 파라미터 및 응답 필드 타입 오류 수정 (#59) * fix: 내책장 편집 모드 진입 시 filteredBookIds 초기화 (#59) * fix: mock API 파라미터 계약 및 라우트 상수 관련 수정 (#59) * fix: PRE_OPINION 아이템 React key 중복 가능성 수정 (#59) * refactor: RootLayout에서 불필요한 overflow-x-clip 제거 (#59) * refactor: BookLogList 삭제 로직을 useBookLogDeleteActions 훅으로 분리 (#59) * [feat] 약속회고 조회 및 댓글 기능 구현 (#103) * feat: 약속 회고상세 UI개발(#96) * refactor: 회고 관련 파일 구조 meeting 폴더로 재구성 (#96) * feat: 댓글 Textarea 높이조절 기능구현(#96) * feat: 약속회고 댓글 기능 구현 (#96) - 약속회고 상세 페이지에 댓글 작성/삭제 기능 추가. - 무한 스크롤로 댓글 목록 조회하며, 약속장 및 댓글 작성자만 삭제 가능. * fix: 약속회고 댓글 에러 처리 및 안정성 개선 (#96) - 댓글 조회 실패 시 에러 토스트 및 안내 메시지 표시. - currentUserId null 체크로 삭제 권한 확인 로직 개선. - 스켈레톤 count 음수값 방어 처리. - Textarea format별 height 기본값 적용. * fix: 약속회고 댓글/상세 조회 mock 처리 개선 (#96) * refactor: 약속회고 댓글 삭제 확인 모달 개선 및 코드 정리 (#96) - window.confirm을 글로벌 모달로 변경 - Mock 함수에서 불필요한 meetingId 파라미터 제거 - Textarea 컴포넌트에서 사용하지 않는 ref 제거. --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> * [feat] 페이지 접근 권한 전역 관리 구현 (#105) * feat: 페이지 접근 권한 전역 관리 구현 (#99) 인터셉터에서 권한 에러 감지 시 커스텀 DOM 이벤트를 dispatch하고, RootLayout의 usePermissionRedirect 훅에서 토스트와 홈 리다이렉트를 처리하여 한 곳에서 통합 관리. - errors.ts: PAGE_ACCESS_ERROR_CODES Set 추가 (6개 에러 코드) - interceptors.ts: permission-denied 커스텀 이벤트 dispatch - usePermissionRedirect: 이벤트 → 토스트 + navigate(HOME) - RootLayout: usePermissionRedirect 훅 연결 - MeetingRetrospectiveCreatePage: 이중 내비게이션 방지 * fix: 권한 에러 중복 이벤트 억제 및 핸들러 방어 코드 추가 (#99) * fix: 권한 에러 시 중복 토스트/모달 방지 (#99) * fix: 약속상세 페이지의 사전의견 버튼에 링크 연동(#106) (#107) --------- Co-authored-by: choiyoungae <109134495+choiyoungae@users.noreply.github.com> Co-authored-by: haeun <110523397+haruyam15@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
🚀 풀 리퀘스트 제안
📋 작업 내용
📸 스크린샷 (선택 사항)
Summary by CodeRabbit
출시 노트
새로운 기능
컴포넌트 업데이트
UI 개선