Skip to content

[FEAT] 동네 추천 코스 등록 지연 이미지 업로드 및 최종 등록 플로우 구현 - #186

Merged
KJun-2 merged 26 commits into
developfrom
feat/local-recommendation-course-creation
Aug 2, 2026
Merged

[FEAT] 동네 추천 코스 등록 지연 이미지 업로드 및 최종 등록 플로우 구현#186
KJun-2 merged 26 commits into
developfrom
feat/local-recommendation-course-creation

Conversation

@KJun-2

@KJun-2 KJun-2 commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

작업 내용

  • 태그 선택의 대표 사진과 장소 선택의 장소 사진을 업로드 없이 즉시 미리보기 처리했습니다.
  • pendingImages 메모리 전용 Zustand 상태에 원본 파일, 압축 파일, Object URL, 압축 Promise를 보관하도록 구성했습니다.
  • pendingImagespersistpartialize에서 제외해 localStorage 직렬화 대상에 포함되지 않습니다.
  • 이미지 교체·삭제·초기화·최종 등록 성공 시 Object URL을 해제합니다.
  • 이미지 압축 실패 시 원본 파일을 사용하도록 폴백 처리했습니다.
  • 방문 순서 선택의 최종 등록에서 대표 사진과 장소 사진을 병렬 업로드하고, 반환 키를 thumbnailKey와 각 장소의 imageKey에 매핑합니다.
  • 장소와 행사 항목을 합쳐 방문 순서에 맞는 코스 생성 Payload를 구성합니다.
  • 장소 검색어 입력 시 300ms 디바운스 후 Kakao Maps 장소 검색을 실행하고, 결과를 검색 결과 카드로 표시합니다.
  • 검색창 아래의 연관 검색어 드롭다운은 노출하지 않습니다.
  • 장소 검색 훅과 영속화용 장소 데이터 변환의 린트 오류를 수정했습니다.

변경 화면

  • /local-recommendation/tag-selection
    • 대표 사진 즉시 미리보기 및 메모리 등록
  • /local-recommendation/place-selection
    • 입력 기반 Kakao 장소 검색 및 결과 카드 표시
  • /local-recommendation/visit-order-selection
    • 방문 순서 확정, 이미지 일괄 업로드, 코스 최종 등록

확인 방법

  1. /local-recommendation/tag-selection에서 대표 사진과 태그를 선택합니다.
  2. /local-recommendation/place-selection에서 장소명을 입력합니다.
  3. 300ms 후 검색 결과 카드가 표시되는지 확인하고, 장소 사진을 등록합니다.
  4. /local-recommendation/visit-order-selection에서 순서를 조정한 뒤 등록합니다.
  5. 대표 사진과 장소 사진이 병렬 업로드되고 코스 상세 화면으로 이동하는지 확인합니다.
  6. 업로드 또는 등록 실패 시 선택한 이미지와 입력값이 유지되는지 확인합니다.
  7. f12 -> 어플리케이션 -> localStorage -> local-recommendation-draft 확인
  8. local-recommendation-draft에 값이 잘 저장되는지 확인해주세요.

체크리스트

  • 로컬 실행 확인
  • 콘솔 에러 없음
  • 반응형 확인
  • 불필요한 console.log 제거
  • develop 최신 반영 완료
  • 관련 없는 파일 변경 없음

스크린샷

image

참고 사항

  • 꼭 포트번호 5173으로 접속해서 place-selection에서 검색시 카카오 맵 api 연동 검색 결과가 나오는지 확인해주세요.

관련 이슈

Closes #185

Summary by CodeRabbit

  • 새로운 기능

    • 카카오맵 기반 장소 검색과 실시간 추천 결과를 제공합니다.
    • 장소 및 대표 이미지를 업로드하고, 자동 압축해 코스 등록에 활용합니다.
    • 선택한 태그를 서버 해시태그와 연결합니다.
    • 방문 장소와 콘텐츠를 원하는 순서로 정렬해 코스로 등록할 수 있습니다.
  • 개선 사항

    • 업로드·등록 중 버튼 상태와 오류 안내를 개선했습니다.
    • 이미지 업로더와 사진 모달의 가로 넘침을 방지했습니다.
    • 검색 요청 지연 처리로 더 안정적인 검색 경험을 제공합니다.

KJun-2 added 24 commits July 30, 2026 22:34
…at/local-recommendation-place-selection

# Conflicts:
#	src/store/localRecommendation.store.ts
…at/local-recommendation-visit-order-selection

# Conflicts:
#	src/store/localRecommendation.store.ts
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontend Ready Ready Preview Aug 1, 2026 6:08pm

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@KJun-2, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 78652973-9d09-413b-9ad8-31a1c80a75bd

📥 Commits

Reviewing files that changed from the base of the PR and between ecd2df9 and 8428ace.

📒 Files selected for processing (9)
  • .env.example
  • src/apis/files.ts
  • src/pages/local-recommendation/place-selection/hooks/useSelectedPlaces.ts
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/tag-selection/index.tsx
  • src/pages/local-recommendation/visit-order-selection/buildCourseRequest.ts
  • src/pages/local-recommendation/visit-order-selection/hooks/useVisitOrderSelection.ts
  • src/store/localRecommendation.store.ts
  • src/utils/imageCompression.ts
📝 Walkthrough

Walkthrough

지역 추천 코스 등록 흐름이 실제 장소 검색, 태그·이미지 보류, 방문 순서 구성, 이미지 병렬 업로드, 코스 등록과 상세 페이지 이동을 사용하도록 변경되었습니다.

Changes

지역 추천 코스 등록

Layer / File(s) Summary
카카오 장소 검색 연동
src/types/kakao-maps.d.ts, src/components/kakaomap/utils/kakaoMap.ts, src/pages/local-recommendation/place-selection/*, src/components/common/SearchBar.tsx, src/pages/local-recommendation/components/SelectionPageLayout.tsx, tests/place-search.test.ts
Kakao Maps Services 타입과 키워드 검색을 추가했습니다. 장소 검색을 300ms 디바운스 비동기 검색으로 변경했습니다. 검색 결과를 PlaceItem으로 변환하고 검색 상태를 표시합니다.
보류 이미지와 장소 선택 상태
src/apis/files.ts, src/utils/imageCompression.ts, src/store/localRecommendation.store.ts, src/pages/local-recommendation/place-selection/*
이미지 압축과 원본 파일 폴백을 추가했습니다. 장소별 pendingImages와 미리보기 URL을 저장·삭제·초기화합니다. 여러 이미지를 병렬 업로드하고 장소 선택 제출 상태를 표시합니다.
태그 선택 결과 연결
src/pages/local-recommendation/tag-selection/*, tests/tag-selection-navigation.test.ts
로컬 태그 라벨을 서버 해시태그 ID로 변환합니다. 대표 이미지와 photoKey, hashtagIds를 태그 선택 결과에 포함합니다. 제출 중복 실행과 제출 오류 표시를 추가했습니다.
방문 순서와 코스 등록
src/pages/local-recommendation/visit-order-selection/*
초안의 장소와 축제를 VisitEvent로 변환합니다. 방문 순서를 코스 항목과 생성 요청으로 변환합니다. 이미지 키를 요청에 연결하고 성공한 courseId로 이동합니다. 제출 중 상태와 오류를 표시합니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PlaceSelectionPage
  participant usePlaceSearch
  participant KakaoPlaces
  participant LocalRecommendationStore
  PlaceSelectionPage->>usePlaceSearch: 검색어 변경
  usePlaceSearch->>KakaoPlaces: keywordSearch 호출
  KakaoPlaces-->>usePlaceSearch: 장소 결과 반환
  usePlaceSearch-->>PlaceSelectionPage: PlaceItem 목록 전달
  PlaceSelectionPage->>LocalRecommendationStore: 장소와 pending 이미지 저장
Loading
sequenceDiagram
  participant VisitOrderPage
  participant useVisitOrderSelection
  participant uploadCourseImages
  participant buildCourseRequest
  VisitOrderPage->>useVisitOrderSelection: 등록 요청
  useVisitOrderSelection->>uploadCourseImages: 이미지 병렬 업로드
  uploadCourseImages-->>useVisitOrderSelection: 이미지 키 반환
  useVisitOrderSelection->>buildCourseRequest: 방문 이벤트와 이미지 키 전달
  buildCourseRequest-->>useVisitOrderSelection: 코스 생성 요청 반환
  useVisitOrderSelection-->>VisitOrderPage: courseId 반환
Loading

Possibly related PRs

  • yeogido/frontend#172: files.ts의 이미지 업로드와 해시태그·코스 생성 API 흐름을 확장합니다.
  • yeogido/frontend#184: 코스 이미지 업로드 기능을 기반으로 uploadCourseImages를 추가합니다.
  • yeogido/frontend#107: 방문 순서 선택과 코스 등록 컴포넌트를 확장합니다.

Suggested reviewers: junehuk

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 태그·장소·방문 순서 연결, 지연 업로드, 이미지 상태 관리, Kakao 검색, 재시도 흐름을 관련 이슈의 요구사항에 맞게 구현했습니다.
Out of Scope Changes check ✅ Passed 변경된 파일과 테스트가 모두 이미지 업로드 지연 처리, 코스 등록 플로우, 장소 검색 및 관련 UI 요구사항에 해당합니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 이미지 업로드 지연과 최종 등록 플로우 구현이라는 PR의 주요 변경 사항을 정확하고 구체적으로 요약합니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/local-recommendation-course-creation

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/pages/local-recommendation/tag-selection/index.tsx (1)

101-127: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

handleComplete에 오류 처리가 없어 실패 시 재시도가 막힙니다.

submitError는 105행에서 빈 문자열로 초기화되는 것 외에는 전체 파일에서 채워지지 않습니다. handleComplete에는 try/catch가 없어, setTagSelection이나 mapTagIdsToHashtagIds에서 예기치 않은 예외가 발생하면 isSubmittingtrue로 남아 제출 버튼이 영구히 비활성화됩니다.

이는 PR 목표인 "등록 실패 시 입력값과 이미지를 유지해 재시도할 수 있도록 합니다"와 어긋납니다. 이 결함의 다운스트림 영향으로, 181-192행의 오류 메시지 UI도 실제로는 표시되지 않는 죽은 코드가 됩니다.

🔧 제안하는 수정 방향
-  const handleComplete = () => {
+  const handleComplete = () => {
     if (!photo || !isReady || isSubmitting) return;

     setIsSubmitting(true);
     setSubmitError('');

-    const hashtagIds = mapTagIdsToHashtagIds(
-      Array.from(selectedTagIds),
-      hashtags,
-      (tagId) => tagDefinitionMap[tagId]?.label
-    );
-
-    setTagSelection({
-      tagIds: Array.from(selectedTagIds),
-      hashtagIds,
-      coverImageKey: null,
-    });
-
-    completeTagSelection({
-      photo,
-      selectedTagIds,
-      photoKey: '',
-      hashtagIds,
-      onComplete,
-      navigate,
-    });
+    try {
+      const hashtagIds = mapTagIdsToHashtagIds(
+        Array.from(selectedTagIds),
+        hashtags,
+        (tagId) => tagDefinitionMap[tagId]?.label
+      );
+
+      setTagSelection({
+        tagIds: Array.from(selectedTagIds),
+        hashtagIds,
+        coverImageKey: null,
+      });
+
+      completeTagSelection({
+        photo,
+        selectedTagIds,
+        photoKey: '',
+        hashtagIds,
+        onComplete,
+        navigate,
+      });
+    } catch (error) {
+      setSubmitError('등록에 실패했습니다. 다시 시도해 주세요.');
+      setIsSubmitting(false);
+    }
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/local-recommendation/tag-selection/index.tsx` around lines 101 -
127, Update handleComplete to wrap the tag-mapping, setTagSelection, and
completeTagSelection flow in try/catch; on any exception, restore isSubmitting
to false and populate submitError with an appropriate failure message so the
existing error UI appears and the user can retry without losing the selected
input or photo.
🧹 Nitpick comments (5)
src/vite-env.d.ts (1)

53-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

src/types/kakao-maps.d.ts와 동일한 카카오 장소 검색 타입이 여기에도 선언되어 있습니다.

KakaoPlacesSearchResult, Places.keywordSearch, Statussrc/types/kakao-maps.d.tsWindow.kakao.maps 선언과 필드까지 동일하게 중복됩니다. 두 파일 중 하나로 소스를 일원화하세요.

Also applies to: 79-91

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vite-env.d.ts` around lines 53 - 70, Remove the duplicated Kakao Places
declarations from the services namespace in src/vite-env.d.ts, including
KakaoPlacesSearchResult usage, Places.keywordSearch, and Status, and rely on the
existing canonical declarations in src/types/kakao-maps.d.ts instead.
src/types/kakao-maps.d.ts (1)

8-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

카카오 장소 검색 SDK 타입이 두 파일에 중복 선언되어 있습니다. KakaoPlacesSearchResult, Places.keywordSearch, StatusWindow.kakao.maps(kakao-maps.d.ts)와 전역 namespace kakao.maps(vite-env.d.ts)에 필드까지 동일하게 각각 선언되어 있습니다. 근본 원인은 동일한 외부 API 표면을 한곳에서 관리하지 않는 것입니다.

  • src/types/kakao-maps.d.ts#L8-L55: 이 파일을 카카오 장소 검색 타입의 단일 소스로 유지하세요. 실제 소비 코드(placeSearch.ts)가 이 선언(Window.kakao.maps.services)을 사용합니다.
  • src/vite-env.d.ts#L53-L91: 중복된 KakaoPlacesSearchResult, Places.keywordSearch, Status 선언을 제거하고, 필요하면 src/types/kakao-maps.d.ts의 타입을 재사용하도록 정리하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/kakao-maps.d.ts` around lines 8 - 55, Keep
src/types/kakao-maps.d.ts lines 8-55 as the single source for Kakao place-search
types, including KakaoPlacesSearchResult, Places.keywordSearch, and Status. In
src/vite-env.d.ts lines 53-91, remove the duplicate declarations and reuse the
types from kakao-maps.d.ts where needed, preserving the existing
Window.kakao.maps.services consumer contract.
tests/place-search.test.ts (1)

20-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

searchPlaces의 상태 분기 테스트가 없습니다.

mapKakaoPlaceToItem만 테스트되고, searchPlacesOK/ZERO_RESULT/오류 상태 분기와 SDK 로드 실패 경로는 테스트되지 않습니다. 이 로직이 이번 레이어의 핵심 신규 동작입니다.

window.kakao.maps.services.PlacesStatus를 목(mock)으로 만들어 세 가지 분기를 검증하는 테스트를 추가하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/place-search.test.ts` around lines 20 - 45, searchPlaces의 핵심 상태 분기와 SDK
로드 실패 경로에 대한 테스트를 추가하세요. window.kakao.maps.services.Places와 Status를 목으로 구성하고 OK,
ZERO_RESULT, 오류 상태에서 각각의 반환 동작을 검증하며, SDK 로드 실패 시의 처리도 확인하세요. 기존
mapKakaoPlaceToItem 테스트는 유지하세요.
src/apis/files.ts (1)

71-78: 🩺 Stability & Availability | 🔵 Trivial

부분 업로드 실패 후 재시도 시 고아 오브젝트가 누적될 수 있습니다.

uploadCourseImages는 모든 파일을 Promise.all로 병렬 업로드합니다. 배치 중 하나라도 실패하면 이미 성공한 업로드는 되돌리지 않습니다. useVisitOrderSelection.handleRegister는 실패 시 초안과 대기 이미지를 유지해 재시도를 허용합니다. 사용자가 재시도하면 이전에 성공한 파일도 다시 업로드되어 새 오브젝트 키가 생성됩니다. 이전 업로드는 참조가 끊긴 채 스토리지에 남습니다.

스토리지 쪽에 미참조 오브젝트 정리 정책(TTL, 배치 정리 작업 등)을 두는 것을 검토하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/apis/files.ts` around lines 71 - 78, Review the upload flow around
uploadCourseImages and useVisitOrderSelection.handleRegister to add a
storage-side cleanup policy for orphaned objects created by failed or retried
batches, such as an appropriate TTL or scheduled batch cleanup. Ensure objects
uploaded before a partial failure and replaced during retry are eventually
removed without affecting successfully referenced uploads.
src/pages/local-recommendation/visit-order-selection/buildCourseRequest.ts (1)

8-36: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

매핑 테이블 키를 basicInfo의 실제 리터럴 타입으로 좁히십시오.

DURATION_TYPE_MAP, TRANSPORT_TYPE_MAP, COMPANION_TYPE_MAPRecord<string, ...>로 선언되어 있습니다. 키 타입이 string이므로 basicInfo.duration 등의 실제 값이 매핑 테이블에 없는 키라도 컴파일러가 감지하지 못합니다. 키 타입을 basicInfo.duration 등의 실제 유니언 타입으로 좁히면, 값 집합이 바뀔 때 누락된 매핑을 컴파일 시점에 발견할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/local-recommendation/visit-order-selection/buildCourseRequest.ts`
around lines 8 - 36, Update DURATION_TYPE_MAP, TRANSPORT_TYPE_MAP, and
COMPANION_TYPE_MAP to use the corresponding basicInfo.duration,
basicInfo.transport, and basicInfo.companion literal union types as their key
types instead of string, while preserving the existing mapped values and
enabling compile-time detection of missing keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/local-recommendation/place-selection/hooks/usePlaceSearch.ts`:
- Around line 50-60: Update setQuery so setIsLoading(true) runs only when
nextQuery differs from the current query state; preserve the existing
empty-query reset behavior and avoid marking loading for repeated submissions
that do not trigger the useEffect search flow.

In `@src/pages/local-recommendation/tag-selection/index.tsx`:
- Around line 59-74: Update the hashtag-loading flow around useEffect and
handleComplete so submission cannot map tags while fetchHashtags is still
pending. Track or reuse the fetch promise and have handleComplete await its
completion before calling mapTagIdsToHashtagIds with the loaded hashtags, while
preserving the existing behavior when the request fails.

In `@src/pages/local-recommendation/visit-order-selection/buildCourseRequest.ts`:
- Around line 63-91: Update buildCourseRequest to resolve the duration,
transport, and companion values from DURATION_TYPE_MAP, TRANSPORT_TYPE_MAP, and
COMPANION_TYPE_MAP before constructing the request; return null if any mapped
value is undefined, and reuse the validated results in the payload.

In
`@src/pages/local-recommendation/visit-order-selection/components/SubmitCourseButton.tsx`:
- Around line 27-32: Remove the minimum clamp from the fontSize property in
SubmitCourseButton’s style and calculate it directly as BUTTON_TEXT_SIZE
multiplied by scale. Keep the 44px minimum clamp on height unchanged.

---

Outside diff comments:
In `@src/pages/local-recommendation/tag-selection/index.tsx`:
- Around line 101-127: Update handleComplete to wrap the tag-mapping,
setTagSelection, and completeTagSelection flow in try/catch; on any exception,
restore isSubmitting to false and populate submitError with an appropriate
failure message so the existing error UI appears and the user can retry without
losing the selected input or photo.

---

Nitpick comments:
In `@src/apis/files.ts`:
- Around line 71-78: Review the upload flow around uploadCourseImages and
useVisitOrderSelection.handleRegister to add a storage-side cleanup policy for
orphaned objects created by failed or retried batches, such as an appropriate
TTL or scheduled batch cleanup. Ensure objects uploaded before a partial failure
and replaced during retry are eventually removed without affecting successfully
referenced uploads.

In `@src/pages/local-recommendation/visit-order-selection/buildCourseRequest.ts`:
- Around line 8-36: Update DURATION_TYPE_MAP, TRANSPORT_TYPE_MAP, and
COMPANION_TYPE_MAP to use the corresponding basicInfo.duration,
basicInfo.transport, and basicInfo.companion literal union types as their key
types instead of string, while preserving the existing mapped values and
enabling compile-time detection of missing keys.

In `@src/types/kakao-maps.d.ts`:
- Around line 8-55: Keep src/types/kakao-maps.d.ts lines 8-55 as the single
source for Kakao place-search types, including KakaoPlacesSearchResult,
Places.keywordSearch, and Status. In src/vite-env.d.ts lines 53-91, remove the
duplicate declarations and reuse the types from kakao-maps.d.ts where needed,
preserving the existing Window.kakao.maps.services consumer contract.

In `@src/vite-env.d.ts`:
- Around line 53-70: Remove the duplicated Kakao Places declarations from the
services namespace in src/vite-env.d.ts, including KakaoPlacesSearchResult
usage, Places.keywordSearch, and Status, and rely on the existing canonical
declarations in src/types/kakao-maps.d.ts instead.

In `@tests/place-search.test.ts`:
- Around line 20-45: searchPlaces의 핵심 상태 분기와 SDK 로드 실패 경로에 대한 테스트를 추가하세요.
window.kakao.maps.services.Places와 Status를 목으로 구성하고 OK, ZERO_RESULT, 오류 상태에서 각각의
반환 동작을 검증하며, SDK 로드 실패 시의 처리도 확인하세요. 기존 mapKakaoPlaceToItem 테스트는 유지하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9413ba47-3cce-44bc-a89b-1536269a687a

📥 Commits

Reviewing files that changed from the base of the PR and between 3e1a737 and a54eb00.

📒 Files selected for processing (30)
  • src/apis/files.ts
  • src/components/common/SearchBar.tsx
  • src/components/kakaomap/utils/kakaoMap.ts
  • src/pages/local-recommendation/components/SelectionPageLayout.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoUploader.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/place-selection/constants/referencePlaces.ts
  • src/pages/local-recommendation/place-selection/hooks/usePlaceSearch.ts
  • src/pages/local-recommendation/place-selection/hooks/useSelectedPlaces.ts
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/place-selection/placeSearch.ts
  • src/pages/local-recommendation/place-selection/types.ts
  • src/pages/local-recommendation/tag-selection/hashtagMapping.ts
  • src/pages/local-recommendation/tag-selection/index.tsx
  • src/pages/local-recommendation/tag-selection/navigation.ts
  • src/pages/local-recommendation/tag-selection/types.ts
  • src/pages/local-recommendation/visit-order-selection/buildCourseRequest.ts
  • src/pages/local-recommendation/visit-order-selection/buildVisitEvents.ts
  • src/pages/local-recommendation/visit-order-selection/components/SubmitCourseButton.tsx
  • src/pages/local-recommendation/visit-order-selection/constants.ts
  • src/pages/local-recommendation/visit-order-selection/hooks/useVisitOrderSelection.ts
  • src/pages/local-recommendation/visit-order-selection/index.tsx
  • src/store/localRecommendation.store.ts
  • src/types/kakao-maps.d.ts
  • src/utils/imageCompression.ts
  • src/vite-env.d.ts
  • tests/place-search.test.ts
  • tests/tag-selection-navigation.test.ts
💤 Files with no reviewable changes (1)
  • src/pages/local-recommendation/place-selection/constants/referencePlaces.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2026-07-26T17:09:23.387Z
Learnt from: KJun-2
Repo: yeogido/frontend PR: 125
File: src/pages/local-recommendation/course-basic-info/components/CompanionSelector.tsx:82-90
Timestamp: 2026-07-26T17:09:23.387Z
Learning: 이 프로젝트의 반응형 스케일 정책에서는 텍스트(폰트) 크기를 설계 기준 값에 전역 `scale`을 곱해 비율대로 스케일하세요. `12px`, `14px`처럼 특정 최소 하한을 강제로 적용하거나(클램프/바닥값) 임의의 픽셀 값으로 제한하지 마세요. 즉, 기준값(디자인 스펙) × `scale` 방식으로 계산해 비율이 유지되도록 구현합니다.

Applied to files:

  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoUploader.tsx
  • src/pages/local-recommendation/visit-order-selection/components/SubmitCourseButton.tsx
  • src/pages/local-recommendation/visit-order-selection/index.tsx
  • src/components/common/SearchBar.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/components/SelectionPageLayout.tsx
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/tag-selection/index.tsx
📚 Learning: 2026-07-26T17:09:27.685Z
Learnt from: KJun-2
Repo: yeogido/frontend PR: 125
File: src/pages/local-recommendation/course-basic-info/components/TransportSelector.tsx:84-91
Timestamp: 2026-07-26T17:09:27.685Z
Learning: In this project’s 390px-based continuous responsive scaling, do not apply an artificial minimum lower bound to text sizes (e.g., avoid clamping via Math.max/min-font-size patterns). Instead, compute typography using proportional ratio scaling: render font sizes as `VALUE * scale` (for example, `DESCRIPTION_FONT_SIZE * scale`). During review, flag any code that enforces a hard minimum text size that breaks the intended proportional scaling.

Applied to files:

  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoUploader.tsx
  • src/pages/local-recommendation/visit-order-selection/components/SubmitCourseButton.tsx
  • src/pages/local-recommendation/visit-order-selection/index.tsx
  • src/components/common/SearchBar.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/components/SelectionPageLayout.tsx
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/tag-selection/index.tsx
📚 Learning: 2026-07-26T17:09:32.991Z
Learnt from: KJun-2
Repo: yeogido/frontend PR: 125
File: src/pages/local-recommendation/tag-selection/components/KeywordSelectionSection.tsx:51-56
Timestamp: 2026-07-26T17:09:32.991Z
Learning: When implementing responsive scaling in this project, prefer direct proportional calculations (e.g., using `VALUE * scale`) without applying artificial minimum lower bounds (clamps) to text sizes or other visual dimensions. Only introduce a lower bound when there is an explicitly documented exception; the primary allowed example is enforcing a 44px minimum touch target for interactive elements. In review, flag new/minimum clamps for text/visual dimensions unless they match an explicitly documented exception.

Applied to files:

  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoUploader.tsx
  • src/pages/local-recommendation/visit-order-selection/components/SubmitCourseButton.tsx
  • src/pages/local-recommendation/visit-order-selection/index.tsx
  • src/components/common/SearchBar.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/components/SelectionPageLayout.tsx
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/tag-selection/index.tsx
📚 Learning: 2026-07-26T17:09:40.303Z
Learnt from: KJun-2
Repo: yeogido/frontend PR: 125
File: src/pages/local-recommendation/tag-selection/components/RepresentativePhotoSection.tsx:107-122
Timestamp: 2026-07-26T17:09:40.303Z
Learning: yeogido/frontend의 반응형 스케일 정책에 맞춰, 텍스트를 포함한 UI 치수는 (디자인 기준값) × 전역 `scale`로 “연속적으로” 계산하고, 12px·14px 같은 최소 폰트/사이즈 하한(floor) 클램프를 적용하지 않는다. `scaleValue`를 쓰더라도 텍스트에 임의의 minimum floor를 추가하거나 `Math.max(value, 12/14)` 같은 보정을 넣지 말 것.

Applied to files:

  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoUploader.tsx
  • src/pages/local-recommendation/visit-order-selection/components/SubmitCourseButton.tsx
  • src/pages/local-recommendation/visit-order-selection/index.tsx
  • src/components/common/SearchBar.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/components/SelectionPageLayout.tsx
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/tag-selection/index.tsx
🔇 Additional comments (28)
src/components/kakaomap/utils/kakaoMap.ts (1)

15-17: LGTM!

Also applies to: 36-36, 89-89

src/pages/local-recommendation/place-selection/types.ts (1)

6-11: LGTM!

src/pages/local-recommendation/place-selection/placeSearch.ts (1)

1-5: LGTM!

Also applies to: 7-25, 27-58

src/pages/local-recommendation/place-selection/hooks/usePlaceSearch.ts (1)

1-6: LGTM!

Also applies to: 14-48, 62-67

src/components/common/SearchBar.tsx (1)

27-27: LGTM!

Also applies to: 38-38, 103-103, 174-176

src/pages/local-recommendation/components/SelectionPageLayout.tsx (1)

37-37: LGTM!

Also applies to: 57-57, 131-131

src/pages/local-recommendation/place-selection/components/PlacePhotoUploader.tsx (1)

56-56: LGTM!

tests/place-search.test.ts (1)

5-44: LGTM!

Also applies to: 57-64

src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx (1)

32-37: 🗄️ Data Integrity & Integration

onSearchChangeonQueryChange가 동일한 핸들러를 참조하는 부분을 확인하세요.

onSearchChangeonQueryChange에 동일한 onSearchChange 함수를 전달합니다. SelectionPageLayout이 동일한 사용자 입력에 대해 두 콜백을 모두 호출하면 setQuery가 중복 실행됩니다. setQuery는 상태를 다시 같은 값으로 설정하므로 즉시 오류는 발생하지 않지만, 의도한 동작인지 SelectionPageLayout의 구현을 확인해야 합니다.

#!/bin/bash
# Description: SelectionPageLayout에서 onQueryChange와 onSearchChange의 호출 시점을 확인합니다.
fd -a 'SelectionPageLayout.tsx' | xargs -I{} rg -n -C6 'onQueryChange|onSearchChange' {}
src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx (1)

60-60: LGTM!

src/utils/imageCompression.ts (1)

1-74: LGTM!

src/store/localRecommendation.store.ts (3)

7-9: LGTM!

Also applies to: 24-24


47-57: 🗄️ Data Integrity & Integration

pendingImages가 새로고침 후 draft.places와 불일치할 수 있는지 확인하세요.

persist 설정의 partializedraft만 localStorage에 저장하고 pendingImages는 메모리에만 유지합니다. 사용자가 장소를 선택한 뒤 페이지를 새로고침하면 draft.places는 복원되지만 해당 장소의 pendingImages 항목(원본 파일, 압축 파일, previewUrl)은 사라집니다.

후속 단계에서 draft.places에 있는 장소를 실제 사진 업로드 대상으로 사용한다면, 새로고침 이후에는 업로드할 파일이 없는 상태로 코스 등록이 진행될 수 있습니다. visit-order-selection 레이어에서 이 경우를 어떻게 처리하는지 확인해야 합니다.

#!/bin/bash
# Description: visit-order-selection 레이어에서 pendingImages 누락을 어떻게 처리하는지 확인합니다.
fd -a 'buildCourseRequest.ts|useVisitOrderSelection.ts' | xargs -I{} rg -n -C6 'pendingImages|compressedFile|originalFile' {}

Also applies to: 68-73, 93-93


164-224: LGTM!

src/pages/local-recommendation/place-selection/hooks/useSelectedPlaces.ts (1)

3-87: LGTM!

src/pages/local-recommendation/place-selection/components/SelectedPlaceSection.tsx (1)

5-42: LGTM!

src/pages/local-recommendation/place-selection/index.tsx (1)

32-58: LGTM!

src/pages/local-recommendation/tag-selection/index.tsx (3)

4-19: LGTM!

Also applies to: 33-34


43-58: LGTM!

Also applies to: 76-89


167-167: LGTM!

Also applies to: 178-192

src/pages/local-recommendation/tag-selection/hashtagMapping.ts (1)

1-31: LGTM!

src/pages/local-recommendation/tag-selection/navigation.ts (1)

6-33: 🗄️ Data Integrity & Integration

photoKey가 실제 업로드 키로 채워지는지 확인이 필요합니다.

CompleteTagSelectionOptions.photoKey의 주석은 "업로드 완료 후 얻은 대표 사진의 오브젝트 키"라고 설명합니다. 그러나 제공된 그래프 컨텍스트에 따르면 tag-selection/index.tsx의 호출부는 photoKey: ''처럼 빈 문자열을 그대로 전달합니다. completeTagSelection은 이 값을 검증 없이 TagSelectionResult.photoKey에 전달합니다.

최종 등록 단계(useVisitOrderSelection.handleRegister)는 coverImageKey를 별도로 계산해 덮어쓰므로, 현재는 이 값이 최종 요청에 영향을 주지 않을 수 있습니다. 하지만 문서 주석과 실제 값이 어긋나 있어 혼동을 줄 수 있습니다. 이 필드가 실제 업로드 키로 채워질 예정인지, 아니면 더 이상 필요 없는 필드인지 확인하십시오.

#!/bin/bash
# photoKey 사용처와 tag-selection/index.tsx 호출부를 확인합니다.
fd -a 'index.tsx' src/pages/local-recommendation/tag-selection --exec rg -n -C6 'photoKey'
rg -n -C3 'photoKey' --glob '*.ts' --glob '*.tsx'
src/pages/local-recommendation/tag-selection/types.ts (1)

10-17: LGTM!

tests/tag-selection-navigation.test.ts (1)

8-75: LGTM!

src/pages/local-recommendation/visit-order-selection/buildVisitEvents.ts (1)

1-52: LGTM!

src/pages/local-recommendation/visit-order-selection/constants.ts (1)

5-27: LGTM!

src/pages/local-recommendation/visit-order-selection/hooks/useVisitOrderSelection.ts (1)

9-67: LGTM!

Also applies to: 69-135, 146-147

src/pages/local-recommendation/visit-order-selection/index.tsx (1)

27-38: LGTM!

Also applies to: 64-72

Comment thread src/pages/local-recommendation/tag-selection/index.tsx
junehuk
junehuk previously approved these changes Aug 1, 2026
Comment thread src/pages/local-recommendation/place-selection/hooks/useSelectedPlaces.ts Outdated

@yoonsunmindd yoonsunmindd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

검색결과 렌더링과 순서 조정, 그리고 등록 실패시에도 local-recommendation-draft값 유지 잘 되는거 확인했습니다!

Image Image Image

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 동네 추천 코스 등록 지연 이미지 업로드 및 최종 등록 플로우 구현

3 participants