Skip to content

[feat] 약속관련 추가 기능 구현#71

Merged
haruyam15 merged 10 commits intodevelopfrom
feat/meetings-67
Feb 12, 2026
Merged

[feat] 약속관련 추가 기능 구현#71
haruyam15 merged 10 commits intodevelopfrom
feat/meetings-67

Conversation

@haruyam15
Copy link
Contributor

@haruyam15 haruyam15 commented Feb 9, 2026

🚀 풀 리퀘스트 제안

약속 생성 - 도서 검색 기능 연결, 모임 상세 API 조회하여 최대 인원 가져오기
약속 수정 - 기능 구현
약속 상세 - 장소 클릭시 카카오 지도로 이동, 툴팁
약속 설정 - 탭 카운터 로직 수정, 스켈레톤 적용

📋 작업 내용

신규 컴포넌트

  • Tooltip — dismissable 모드(기본값 열림 + 닫기가능) 지원하는 툴팁 컴포넌트
  • MeetingApprovalListSkeleton — 약속 승인 목록 로딩 UI

신규 훅

  • useUpdateMeeting — 약속 수정 mutation, 성공 시 상세/승인 쿼리 캐시 무효화
  • useMeetingForm — 약속생성 폼 로직 개선 (수정 모드 지원, 통합 상태 관리)

API 레이어

  • meetings.types.ts — 약속 수정 관련 타입 추가
  • meetings.endpoints.ts — 약속 수정(UPDATE) 엔드포인트 추가
  • meetings.api.ts — 약속 수정(updateMeeting) 함수 추가
  • meetings.mock.ts — 수정 관련 목데이터 추가, 목데이터 사용여부 env파일로 관리

기존 컴포넌트 변경 사항

  • MeetingCreatePage — 도서 검색 모달, 최대 인원 입력, Tooltip 가이드 추가
  • MeetingSettingPage — 수정 모드 지원, 기존 데이터 로드 및 초기화
  • MeetingApprovalList — 스켈레톤 로딩 상태 추가
  • MeetingDetailInfo — 도서 정보 표시 로직 개선

삭제

  • useMeetingApprovalsCount 제거 (미사용)

📄 기타

장소 선택 지도 모달은 디자인 확정되면 새로 이슈파서 작업하겠습니다

Summary by CodeRabbit

  • 새로운 기능

    • 회의 수정(업데이트) 및 편집 모드 지원 추가
    • 전역 툴팁 시스템 도입 및 컴포넌트 가이드에 툴팁 섹션 추가
    • 승인 목록 로딩용 스켈레톤 컴포넌트 추가
  • 개선사항

    • 승인 목록 페이징이 외부 데이터로 제어되도록 변경
    • 회의 생성/편집 폼을 중앙화한 form 데이터 모델로 리팩터링
    • 장소 검색 오류 표시 및 Kakao 지도 SDK 로딩의 지연/재시도 및 오류 노출 도입
    • 전역 알림에 닫힘 콜백(onClose) 지원 추가
    • 라우트에 회의 수정 경로 추가 (생성/수정 페이지 재사용)

- Tooltip 컴포넌트를 추가하여 사용자에게 UI 설명 기능 제공.
- 약속 승인 목록에 스켈레톤 로딩 UI를 추가하여 UX 개선.
- useUpdateMeeting hook을 추가하여 약속 수정 로직을 분리하고, MeetingCreatePage와 MeetingSettingPage의 폼 검증 및 사용자 경험 개선.
- 약속 관련 API 엔드포인트 및 타입 정의를 확장하여 기능 지원 강화.
@haruyam15 haruyam15 self-assigned this Feb 9, 2026
@haruyam15 haruyam15 added the feat 새로운 기능 추가 label Feb 9, 2026
@haruyam15 haruyam15 linked an issue Feb 9, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Radix 기반 Tooltip을 전역 도입하고, 회의 생성/수정 폼을 중앙화된 formData로 리팩터링했으며 승인 리스트를 prop 기반으로 전환했습니다. Kakao Maps SDK를 동적 로드하도록 추가하고 지도/검색 훅에 에러 상태 및 비동기 초기화를 도입했습니다.

Changes

Cohort / File(s) Summary
Tooltip 인프라
package.json, src/App.tsx, src/shared/ui/Tooltip.tsx, src/pages/ComponentGuide/ComponentGuidePage.tsx
Radix Tooltip 의존성 추가 및 TooltipProvider를 루트에 적용; Tooltip 컴포넌트(Provider/Root/Trigger/Content) 구현 및 가이드 페이지에 섹션 추가.
회의 생성/수정 및 API 확장
src/pages/Meetings/MeetingCreatePage.tsx, src/routes/index.tsx, src/features/meetings/meetings.api.ts, src/features/meetings/meetings.endpoints.ts, src/features/meetings/meetings.types.ts, src/features/meetings/hooks/useUpdateMeeting.ts, src/features/meetings/index.ts
Meeting update 엔드포인트·타입 추가, useUpdateMeeting 훅 추가; Create 페이지에 edit 모드(경로 param) 및 업데이트 흐름 도입; 라우트 추가.
회의 폼 리팩토링
src/features/meetings/hooks/useMeetingForm.ts, src/features/meetings/hooks/index.ts
개별 useState들을 단일 formData로 통합하고 initialData로 edit 모드 지원; 훅 반환이 formData·handlers 중심으로 변경됨.
승인 리스트 및 페이징 변경
src/features/meetings/components/MeetingApprovalList.tsx, src/features/meetings/components/MeetingApprovalListSkeleton.tsx, src/features/meetings/components/index.ts, src/pages/Meetings/MeetingSettingPage.tsx
Approval 리스트를 prop 기반(data, currentPage, onPageChange)으로 전환, 스켈레톤 컴포넌트 추가, Setting 페이지에서 PENDING/CONFIRMED 별 페칭·페이징 처리로 변경.
훅 시그니처·캐시 무효화 조정
src/features/meetings/hooks/useDeleteMeeting.ts, src/features/meetings/hooks/useRejectMeeting.ts, src/features/meetings/hooks/useMeetingApprovals.ts, src/features/meetings/hooks/useMeetingApprovalsCount.ts, src/features/meetings/hooks/useCreateMeeting.ts
useDeleteMeeting·useRejectMeetinggatheringId 인자 추가 및 해당 gathering의 meetings 캐시 무효화; useMeetingApprovalsCount 제거 및 일부 JSDoc·enabled 로직 정리.
지도 통합·에러 처리
src/features/meetings/hooks/useKakaoMap.ts, src/features/meetings/hooks/useKakaoPlaceSearch.ts, src/features/meetings/loadKakaoSdk.ts, index.html, src/features/meetings/components/MeetingDetailInfo.tsx, src/features/meetings/components/PlaceSearchModal.tsx
Kakao SDK 동적 로더 추가, SDK/검색 훅에 error 상태 및 비동기 initialize 도입, 정적 SDK 스크립트 제거, MapModal 제거 후 외부 맵 오픈으로 변경 및 지도/검색 오류 UI 추가.
UI·텍스트·사소 변경
src/features/meetings/components/MeetingApprovalItem.tsx, src/features/meetings/components/PlaceList.tsx, src/features/meetings/components/MeetingDetailButton.tsx, src/features/meetings/meetings.mock.ts
MeetingApprovalItemgatheringId prop 추가(버튼 라벨 일부 변경), 버튼에 cursor-pointer 추가, 상세 설명 조건 반전, mock 데이터에 책 저자/출판사 필드 추가.
토픽 헤더 Tooltip 적용 및 mock 플래그 변경
src/features/topics/components/TopicHeader.tsx, src/features/topics/topics.api.ts
사전 의견 확인 버튼에 Tooltip 적용(권한 없을 때 설명 표시), mock 플래그를 환경변수 기반 USE_MOCK로 전환.
전역 모달 API 변경
src/store/globalModalStore.ts
openAlert에 선택적 onClose 콜백 추가(알림 종료 후 후속 처리 지원).
내보내기·타입 조정
src/features/meetings/meetings.types.ts, src/features/meetings/index.ts, src/features/meetings/hooks/useMeetingApprovalsCount.ts, src/features/meetings/hooks/index.ts
Create/Update 타입 필드명 변경 및 책 저자/출판사 필드 추가, useMeetingApprovalsCount 제거 및 useUpdateMeeting 재내보내기 등 public 타입/exports 조정.

Sequence Diagram

sequenceDiagram
    actor User
    participant MeetingCreatePage as MeetingCreatePage
    participant useMeetingDetail as useMeetingDetail
    participant useMeetingForm as useMeetingForm
    participant useUpdateMeeting as useUpdateMeeting
    participant API as API
    participant Router as Router

    User->>MeetingCreatePage: 수정 페이지 접근 (/gatherings/:gatheringId/meetings/:meetingId/update)
    MeetingCreatePage->>useMeetingDetail: meetingId로 상세 조회
    useMeetingDetail->>API: GET /api/meetings/:id
    API-->>useMeetingDetail: meeting 데이터
    useMeetingDetail-->>MeetingCreatePage: 초기 데이터 반환
    MeetingCreatePage->>useMeetingForm: initialData로 폼 초기화
    useMeetingForm-->>MeetingCreatePage: formData, handlers 반환
    User->>MeetingCreatePage: 폼 수정 후 제출
    MeetingCreatePage->>useUpdateMeeting: UpdateMeetingRequest 전송
    useUpdateMeeting->>API: PATCH /api/meetings/:id
    API-->>useUpdateMeeting: 업데이트 응답
    useUpdateMeeting->>useUpdateMeeting: 관련 캐시 무효화 (detail, approvals)
    useUpdateMeeting-->>MeetingCreatePage: 성공 콜백
    MeetingCreatePage->>Router: 상세 페이지로 이동
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • mgYang53
  • choiyoungae
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경사항을 반영하고 있습니다. 약속 관련 기능 추가를 명확히 나타내고 있으며, 도서 검색, 약속 수정, 카카오 지도 연결, 툴팁 등 여러 기능이 포함된 포괄적인 변경에 대한 적절한 요약입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/meetings-67

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
src/pages/Meetings/MeetingCreatePage.tsx (2)

443-461: 모달 마운트/언마운트 조건부 렌더링 — 의도적이라면 OK

{isPlaceSearchOpen && <PlaceSearchModal .../>} 패턴은 닫을 때마다 컴포넌트를 언마운트하여 카카오 지도를 매번 재초기화합니다. PlaceSearchModal 내부의 cleanup 패턴과 일치하므로 현재 구조에서는 문제없습니다.

단, 잦은 열기/닫기 시 지도 SDK 초기화 비용이 체감될 수 있으니, 사용자 피드백에 따라 open prop만으로 제어하는 방식도 고려해 볼 수 있습니다.


85-85: totalMembers0이면 || 1 폴백이 동작합니다 — 의도 확인

gathering?.totalMembers || 1totalMembers0이거나 undefined일 때 모두 1로 폴백합니다. 멤버가 0인 모임은 현실적으로 없으므로 문제없지만, 명시적으로 ?? 1 (nullish coalescing)을 사용하면 의도가 더 명확해집니다.

제안
- const gatheringMaxCount = gathering?.totalMembers || 1
+ const gatheringMaxCount = gathering?.totalMembers ?? 1

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@src/features/meetings/components/MeetingDetailInfo.tsx`:
- Around line 123-129: The onClick opens a Kakao map URL by interpolating
location.name directly, which can break for spaces or special chars; update the
onClick in MeetingDetailInfo.tsx to URL-encode the place name (and
stringify/encode the lat/lng if desired) before building the URL (e.g., use
encodeURIComponent on location.name and on location.latitude/location.longitude)
and then call window.open with the encoded values so the generated URL is valid
and safe.

In `@src/pages/Meetings/MeetingCreatePage.tsx`:
- Around line 155-167: The current unified guard in MeetingCreatePage.tsx blocks
submission in edit mode because it always requires bookThumbnail, bookAuthors,
and bookPublisher even though UpdateMeetingRequest doesn't include book data;
split the validation into two branches: when creating (e.g., !isEdit) require
bookId, bookName, bookThumbnail, bookAuthors, bookPublisher, startDate,
startTime, endDate, endTime, but when updating (e.g., isEdit or when building an
UpdateMeetingRequest) only validate the fields that UpdateMeetingRequest needs
(startDate/startTime/endDate/endTime and whatever update-specific fields are
required) and allow missing book* fields; update the conditional that currently
references bookId/bookThumbnail/bookAuthors/bookPublisher to check mode and
validate accordingly so edit submissions no longer silently return.
- Around line 57-90: Replace broad Zustand subscription and three separate
useEffect handlers with a selector-based subscription for just the modal action
and a single consolidated effect that enforces error priority: use const
openError = useGlobalModalStore(state => state.openError) to avoid redundant
re-renders, then combine the gatheringId check and API error handling into one
useEffect that first validates gatheringId (call openError and navigate as
currently done), else checks gatheringError before meetingError and calls
openError once with the appropriate message and navigation logic; keep
references to navigate, openError, gatheringError, meetingError, and gatheringId
in the effect dependencies.
- Around line 187-199: The code awaits openAlert which returns void so
navigation runs immediately; update the onSuccess handler in
updateMutation.mutate (and the parallel createMutation handler around the noted
create-mode block) to either call openConfirm and await its promise or move the
navigate(ROUTES.GATHERING_DETAIL(gatheringId), { replace: true }) into the
confirmation callback of the modal UI; specifically replace await openAlert('약속
수정 완료', ...) with an awaited call to openConfirm(...) (or call openAlert(...)
and perform navigate inside the modal’s onConfirm handler) so navigation happens
only after user confirmation, keeping updateMutation, createMutation,
openAlert/openConfirm and navigate as the referenced symbols.

In `@src/pages/Meetings/MeetingSettingPage.tsx`:
- Around line 53-64: The effect currently calls openError twice when both
isPendingError and isConfirmedError are true; change the logic in the useEffect
so it only opens a single modal by selecting one error to show (e.g., prefer
pendingError over confirmedError or merge messages) and call openError once with
the chosen error; update the dependency references (useEffect uses
isPendingError, isConfirmedError, pendingError, confirmedError, openError,
navigate) and ensure the navigate callback uses the same chosen error handling
path.
- Around line 18-19: The component currently converts gatheringIdParam to Number
which yields NaN for undefined and is passed into useMeetingApprovals; add
validation so queries aren't fired with an invalid id: compute an isValidId
boolean from useParams (e.g., const isValidId = typeof gatheringIdParam ===
'string' && /^\d+$/.test(gatheringIdParam)), then either (A) pass enabled:
isValidId into useMeetingApprovals call or (B) early-return/navigate when
!isValidId (e.g., navigate('/', { replace: true }) and return null). Update
occurrences of gatheringId usage to rely on the validated
Number(gatheringIdParam) only when isValidId is true. Ensure the option name
enabled is provided to useMeetingApprovals so the hook doesn't run with NaN.
- Around line 101-126: When loading flags are false but data is undefined
(error) the tab shows blank; update both the PENDING and CONFIRMED TabsContent
blocks to render a fallback UI when data is missing instead of rendering
nothing: i.e., change the conditional that currently checks isPendingLoading ?
MeetingApprovalListSkeleton : (pendingData && <MeetingApprovalList ...>) to
render either MeetingApprovalList when pendingData exists, otherwise render
MeetingApprovalListSkeleton or a dedicated error/fallback component (same for
isConfirmedLoading/confirmedData). Ensure you keep the existing props
(data={...}, currentPage={...}, onPageChange={...}) and update both PENDING and
CONFIRMED branches so the UI shows a skeleton/fallback during error states until
navigate runs.

In `@src/shared/ui/Tooltip.tsx`:
- Around line 158-164: handleDismiss currently only calls onDismiss and skips
close(), which leaves the Tooltip open when a consumer supplies onDismiss;
change handleDismiss so it always calls close() first and then, if onDismiss
exists, invoke onDismiss() as a follow-up callback (preserve order and any
args), ensuring the Tooltip closes in dismissable mode even when onDismiss is
provided; update the handleDismiss function and any references to it to reflect
this behavior.
🧹 Nitpick comments (7)
src/features/meetings/hooks/useCreateMeeting.ts (1)

30-30: 주석 일관성 개선 제안

동일한 패턴을 사용하는 다른 훅들(useRejectMeeting, useConfirmMeeting, useDeleteMeeting)은 (리스트 + 카운트) 상세 정보를 주석에 포함하고 있습니다. 의도적으로 제네릭하게 변경한 것이라면 다른 훅들도 함께 업데이트하는 것이 일관성 측면에서 좋을 것 같습니다.

일관성을 위한 제안

옵션 1: 이 파일을 다른 훅들과 동일하게 상세 명시

-      // 약속 승인 관련 모든 캐시 무효화
+      // 약속 승인 관련 모든 캐시 무효화 (리스트 + 카운트)

옵션 2: 다른 훅들(useRejectMeeting, useConfirmMeeting, useDeleteMeeting)도 이 파일처럼 제네릭하게 변경

// 약속 승인 관련 모든 캐시 무효화
src/features/meetings/meetings.types.ts (1)

103-104: TODO 코멘트가 남아있습니다.

Todo:실제 응답값이랑 비교해봐야 함 — 실제 API 연동 시 응답 타입 검증 후 제거가 필요합니다.

이 TODO를 추적할 이슈를 생성해드릴까요?

src/shared/ui/Tooltip.tsx (1)

44-95: non-dismissable 모드에서 불필요한 internalOpen 상태 초기화

internalOpen은 dismissable 모드에서만 사용되지만, 모든 인스턴스에서 useState(true)로 초기화됩니다. 현재 동작에 문제는 없지만, 의도를 명확히 하려면 dismissable일 때만 초기화하는 것이 좋습니다.

src/features/meetings/hooks/useMeetingForm.ts (1)

203-212: updateField에서 errors 참조가 stale할 가능성

updateField 내부에서 errors를 직접 읽고 있습니다. errors는 렌더 스코프 변수이므로 현재 렌더 시점 값을 참조하지만, 만약 향후 useCallback으로 감싸면서 의존성에 errors를 빠뜨리면 stale closure 버그로 이어질 수 있습니다.

현재는 매 렌더마다 재생성되므로 문제없지만, 방어적으로 clearError만 호출하고 조건 검사를 clearError 내부로 위임하는 방식도 고려해볼 수 있습니다.

src/pages/Meetings/MeetingCreatePage.tsx (3)

92-92: || 대신 ?? 사용 권장

totalMembers0일 때 ||1로 폴백되지만 ??0을 유지합니다. null/undefined만 폴백하려는 의도라면 ??가 더 정확합니다.

-  const gatheringMaxCount = gathering?.totalMembers || 1
+  const gatheringMaxCount = gathering?.totalMembers ?? 1

276-281: errorMessage prop을 안내 메시지 용도로 사용 — 의미/스타일 불일치 가능

errorMessage'도서는 수정이 불가합니다.'를 전달하면, Container.Title 내부에서 에러 스타일(빨간색 텍스트 등)로 렌더링될 수 있습니다. 안내 메시지라면 별도 prop이나 helper text 방식이 더 적절합니다.


142-144: 불필요한 래퍼 함수 — setBook을 직접 전달 가능

handleBookSelectionsetBook을 그대로 호출하는 래퍼입니다. onSelectBook={setBook}으로 직접 전달하면 됩니다.

-  const handleBookSelection = (book: SearchBookItem) => {
-    setBook(book)
-  }

Line 461에서:

-            onSelectBook={handleBookSelection}
+            onSelectBook={setBook}

- 약속 생성/수정 전 confirm 모달을 노출하도록 처리 추가.
- useDeleteMeeting, useRejectMeeting에 gatheringId를 전달하여
  약속 거부/삭제 후 모임 약속 목록 캐시를 함께 무효화.
- openAlert에 onClose 콜백을 추가하여 모달 닫힘 후 동작 지원.
- MeetingCreatePage 에러 처리 useEffect 통합 및 핸들러 분리 리팩토링.
- MeetingSettingPage gatheringId 파싱 안전성 개선.
index.html의 정적 스크립트 태그를 제거하고 loadKakaoSdk.ts 싱글톤
로더를 추가하여 지도 사용 시점에만 SDK를 동적으로 로드.

- SDK 로드 실패 시 HTTP 상태 코드별 에러 메시지 표시
- useKakaoMap, useKakaoPlaceSearch에 error 상태 추가
- PlaceSearchModal에 SDK 로드 오류 및 검색 오류 UI 오버레이 추가
- MeetingDetailButton의 isEnabled 조건 반전 버그 수정
- MeetingCreatePage 약속명 24자 제한(slice) 적용
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/meetings/hooks/useKakaoMap.ts (1)

62-109: ⚠️ Potential issue | 🟠 Major

SDK 로드 성공 시 error 상태가 초기화되지 않습니다.

첫 시도 실패 → setError(message) → 재시도 성공 시 error가 이전 에러 메시지를 유지합니다.
UI에서 error를 표시하고 있다면 성공했음에도 에러 메시지가 남아 있게 됩니다.

🐛 수정 제안: 성공 경로에서 error 초기화
     mapRef.current = map

     infowindowRef.current = new kakao.maps.InfoWindow({ zIndex: 1 })
+    setError(null)
     setIsInitialized(true)
🤖 Fix all issues with AI agents
In `@src/features/meetings/hooks/useKakaoPlaceSearch.ts`:
- Around line 51-56: In useKakaoPlaceSearch, remove the hardcoded "(400 Bad
Request)" from the error message and instead surface the actual error/status
details (e.g., include the Status value or error message) when Status.ERROR
occurs; also stop calling onSearchSuccess in the error path—either add and call
an onSearchError callback (new prop) or simply avoid invoking onSearchSuccess
when setting setError/setPlaces([]); update references in that hook
(Status.ERROR handling, setError, setPlaces, onSearchSuccess) accordingly so
logs and UI reflect the real error cause.

In `@src/features/meetings/loadKakaoSdk.ts`:
- Line 34: Remove the unused parameter from the onerror handler in
loadKakaoSdk.ts: locate the script.onerror assignment (the handler currently
declared as script.onerror = (_scriptEvent) => { ... }) and change it to a
no-arg arrow function (script.onerror = () => { ... }) so the unused
`_scriptEvent` parameter is eliminated and the ESLint no-unused-vars failure is
resolved.
- Around line 30-32: The Promise created in loadKakaoSdk uses script.onload →
window.kakao.maps.load(() => resolve()) but if the maps.load callback throws the
Promise never settles; update the callback in loadKakaoSdk.ts to wrap
window.kakao.maps.load's callback body in try/catch and call reject(err) on
exceptions (and optionally call resolve only on success), and also add a safe
timeout fallback that rejects the Promise after a reasonable delay to avoid
infinite waiting; reference the script.onload handler, window.kakao.maps.load,
and the resolve/reject closures when making the change.

In `@src/pages/Meetings/MeetingCreatePage.tsx`:
- Around line 57-82: When gatheringId is 0 the first useEffect opens an error
modal and the second useEffect can also open one if useGatheringDetail(0) fails;
update the second useEffect (the API error handler that checks gatheringError
and meetingError) to early-return when gatheringId === 0 so it does nothing for
that invalid-id case—i.e., add a check for gatheringId (the same gatheringId
used above) before evaluating gatheringError/meetingError and calling
openError/navigate to prevent duplicate modals.
🧹 Nitpick comments (3)
src/features/meetings/loadKakaoSdk.ts (1)

38-56: onerror 후 동일 URL을 fetch로 재요청하여 상태 코드를 확인하는 접근.

스크립트 태그 로드와 fetch는 CORS/CSP 정책 적용이 다를 수 있어, 실제 실패 원인과 다른 상태 코드를 받을 수 있습니다. 진단용으로는 괜찮지만 이 점을 인지하고 계시면 됩니다.

src/features/meetings/components/MeetingApprovalItem.tsx (1)

45-48: TODO 코멘트 — 동시간대 승인 충돌 UX 개선 필요

동시간대 약속 승인 불가 시 서버에서 특정 에러 코드를 내려줄 경우, onError에서 해당 코드를 분기하여 안내 메시지를 표시하면 좋겠습니다. 이슈로 트래킹할까요?

src/pages/Meetings/MeetingCreatePage.tsx (1)

229-231: 생성 모드에서 isMeetingLoading이 불필요하게 버튼을 비활성화할 수 있음

isLoading = isGatheringLoading || isMeetingLoading인데, 생성 모드에서도 useMeetingDetail(0)이 실행되면 isMeetingLoading이 잠시 true가 될 수 있습니다. 이는 위의 enabled 조건 이슈와 연관됩니다.

- const isLoading = isGatheringLoading || isMeetingLoading
+ const isLoading = isGatheringLoading || (isEditMode && isMeetingLoading)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/features/meetings/loadKakaoSdk.ts`:
- Around line 38-61: The current fetch-based diagnostic in loadKakaoSdk.ts
(inside the fetch(script.src) handler used after script.onerror) can produce
misleading messages if the fetch returns 200 while the original script load
failed (TOCTOU); update the fetch.then branch to special-case a 200 response and
produce a distinct, non-misleading message (e.g., indicate the script tag
errored but fetch returned 200—possible CDN/cache/CORS discrepancy) instead of
treating it like a generic success or showing "HTTP 200" failure; keep the
existing kakaoStatusMessages mapping for non-200 codes, log the clearer
200-specific message with console.error, and reject the same Promise (reject(new
Error(...))) so callers still receive an error.
🧹 Nitpick comments (1)
src/features/meetings/loadKakaoSdk.ts (1)

23-67: 타임아웃 없이 Promise가 영구 pending될 수 있습니다.

스크립트 로드가 onload/onerror 어느 쪽도 발생하지 않는 극단적 상황(네트워크 hang 등)에서 kakaoSdkPromise가 영원히 pending → 이후 모든 호출도 같은 pending Promise를 받아 지도 기능 전체가 먹통됩니다.

안전장치로 타임아웃을 추가하면 재시도 경로가 열립니다.

💡 타임아웃 제안
 kakaoSdkPromise = new Promise((resolve, reject) => {
+  const TIMEOUT_MS = 15_000
+  const timer = setTimeout(() => {
+    kakaoSdkPromise = null
+    reject(new Error('카카오 지도 SDK 로드 시간이 초과되었습니다.'))
+  }, TIMEOUT_MS)
+
   const script = document.createElement('script')
   // ...
   script.onload = () => {
+    clearTimeout(timer)
     window.kakao.maps.load(() => resolve())
   }
   script.onerror = () => {
+    clearTimeout(timer)
     kakaoSdkPromise = null
     // ...
   }
 })

Copy link
Contributor

@mgYang53 mgYang53 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다 👍

const newError: ValidationErrors = {}

if (!bookId || !bookName) {
if (
Copy link
Contributor

Choose a reason for hiding this comment

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

bookThumbnail이 없는 경우는 없나요??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

먼가 과거 커밋 같은데요!! 코드가 달라여

image

Copy link
Contributor

@mgYang53 mgYang53 Feb 11, 2026

Choose a reason for hiding this comment

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

현재 버전에서 책 썸네일을 필수값으로 검증하는데 만약에 썸네일이 없는 책 데이터가 있을 수도 있을까봐 여쭤본거에요!

그런 경우가 없다면 상관없습니다~

Copy link
Contributor Author

Choose a reason for hiding this comment

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

생각 못해봤는데 확인해보니까 썸네일이 없는경우 대체이미지가 있어서 괜찮습니다!
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 약속관련 추가 기능 구현

2 participants