Skip to content

[FEAT] 구글 플레이스 이미지 연동 - #301

Merged
junehuk merged 15 commits into
developfrom
feature/google-places-images
Aug 9, 2026
Merged

[FEAT] 구글 플레이스 이미지 연동#301
junehuk merged 15 commits into
developfrom
feature/google-places-images

Conversation

@KJun-2

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

Copy link
Copy Markdown
Collaborator

작업 내용

  • 구글 플레이스 API 연동을 통한 장소 사진 조회 기능 추가
  • 코스 등록 및 지역 추천 장소 선택 화면에 장소 사진 모달/썸네일 적용
  • 영업시간 조회 로직 리팩토링

변경 화면

  • 코스 등록 - 장소 선택 화면
  • 지역 추천 - 장소 선택 화면
  • 지역 추천 - 선택 결과 카드

확인 방법

  1. 코스 등록(/admin/courses) 또는 지역 추천 페이지(/local-recommendation/place-selection)에서 장소를 검색한다.
  2. 검색된 장소 목록에서 장소를 선택하고 사진 모달이 정상적으로 노출되는지 확인한다.
  3. 선택된 장소의 영업시간 정보가 정상적으로 표시되는지 확인한다.

체크리스트

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

스크린샷

image image

참고 사항

관련 이슈

Closes #300

Summary by CodeRabbit

  • 새 기능

    • 장소 검색 결과와 선택 목록에 실제 장소 사진을 표시합니다.
    • 사진이 없을 때 카테고리 아이콘이나 위치 마커를 대체 이미지로 제공합니다.
    • 장소 사진 없이도 선택을 완료할 수 있습니다.
    • 코스 등록 시 선택한 사진이 없으면 장소의 원격 이미지를 자동으로 업로드합니다.
    • 장소 사진 조회 요청의 안정성과 응답 속도가 개선되었습니다.
  • 버그 수정

    • 좋아요 등록 요청 방식을 일관되게 개선했습니다.
    • 장소 사진 및 이미지 다운로드 실패를 안전하게 처리합니다.

@vercel

vercel Bot commented Aug 8, 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 9, 2026 5:24am

@coderabbitai

coderabbitai Bot commented Aug 8, 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: 28 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: c6d554ff-14dd-4b94-8eed-d3cfde9ab1c3

📥 Commits

Reviewing files that changed from the base of the PR and between 4386dff and 45179b2.

📒 Files selected for processing (14)
  • api/google-places/fetchWithTimeout.ts
  • api/google-places/image.ts
  • api/google-places/imageProxy.ts
  • api/google-places/photo.ts
  • api/google-places/placeHours.ts
  • api/google-places/placePhoto.ts
  • src/apis/courses.ts
  • src/apis/localRecommendations.ts
  • src/pages/admin/course-registration/place-selection/index.tsx
  • src/pages/admin/course-registration/visit-order/uploadAdminCourseImages.ts
  • src/pages/admin/course-registration/visit-order/useAdminCourseVisitOrder.ts
  • src/pages/local-recommendation/components/SelectionResultCard.tsx
  • vercel.json
  • vite.config.ts
📝 Walkthrough

Walkthrough

Google Places 사진 조회 API와 클라이언트 훅을 추가했습니다. 장소 선택 화면은 조회 사진을 표시하고, 사진이 없으면 카테고리별 아이콘을 사용합니다. 원격 사진은 코스 이미지 업로드 대상으로 변환합니다.

Changes

Google Places 사진 기능

Layer / File(s) Summary
사진 조회 계약과 Google API 호출
api/google-places/parseRequest.ts, api/google-places/placePhoto.ts
입력값 정규화와 사진 조회 타입을 추가했습니다. 텍스트 검색, 장소 상세 조회, 사진 미디어 조회를 순서대로 실행합니다.
사진 API 엔드포인트와 런타임 연결
api/google-places/photo.ts, vite.config.ts, vercel.json, api/google-places/placeHours.ts
Edge 핸들러와 개발 서버 미들웨어를 추가했습니다. JSON 검증, 캐시, 속도 제한, HTTP 오류 응답, 라우트 rewrite를 처리합니다.
클라이언트 사진 조회와 동시성 제어
src/apis/googlePlacesPhoto.ts, src/hooks/utils/createRequestQueue.ts, src/hooks/usePlacePhotos.ts, src/hooks/usePlaceOpeningHours.ts
사진 조회 클라이언트와 React Query 훅을 추가했습니다. 사진 요청은 동시 실행 수를 제한하고 캐시 시간을 적용합니다. 운영 시간 요청도 공통 큐를 사용합니다.
관리자 장소 선택과 이미지 업로드
src/pages/admin/course-registration/place-selection/index.tsx, src/pages/admin/course-registration/visit-order/...
관리자 장소 카드가 조회 사진을 우선 사용합니다. 사진이 없으면 카테고리별 대체 아이콘을 표시합니다. 원격 사진을 File 객체로 변환해 업로드합니다.
지역 추천 장소 시각 표현과 선택 처리
src/pages/local-recommendation/components/..., src/pages/local-recommendation/place-selection/..., src/store/localRecommendation.store.ts
장소 카드에 사진·마커 시각 모드를 추가했습니다. 사진 선택 모달의 필수 여부와 확인 버튼 비활성화 조건을 조정했습니다.

기존 API 동작 정리

Layer / File(s) Summary
좋아요 메서드와 API 클라이언트 정리
src/apis/courses.ts, src/apis/localRecommendations.ts
장소 좋아요 등록 요청을 PUT으로 변경했습니다. 추천 생성 API는 정적 import된 apiClient를 사용합니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant PlaceSelection
  participant usePlacePhotos
  participant PhotoApi
  participant GooglePlaces
  PlaceSelection->>usePlacePhotos: 장소 목록과 위치 정보 전달
  usePlacePhotos->>PhotoApi: 장소별 사진 조회 요청
  PhotoApi->>GooglePlaces: 텍스트 검색 및 사진 조회
  GooglePlaces-->>PhotoApi: 사진 URI 또는 null
  PhotoApi-->>usePlacePhotos: 사진 결과 반환
  usePlacePhotos-->>PlaceSelection: 사진 상태 맵 반환
  PlaceSelection-->>PlaceSelection: 사진 또는 카테고리 아이콘 렌더링
Loading

Possibly related PRs

  • yeogido/frontend#114: 동일한 장소 선택 및 사진 업로드 컴포넌트를 확장합니다.
  • yeogido/frontend#186: 기존 장소 선택, 대기 이미지, 업로드 흐름을 기반으로 사진 조회를 연결합니다.
  • yeogido/frontend#252: usePlaceOpeningHours와 Google Places 요청 큐 변경이 관련됩니다.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning [#300]과 직접 관련이 없는 좋아요 등록 API의 HTTP 메서드 변경이 포함되어 있습니다. src/apis/courses.ts의 좋아요 등록 API 변경을 별도 PR로 분리하거나 해당 변경의 요구사항을 명시하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed [#300] 사진 조회 API, 사진 모달과 썸네일, 장소 선택 결과 카드 표시 기능을 구현했습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 Google Places 사진 조회와 화면 연동이라는 풀 리퀘스트의 주요 변경 사항을 간결하게 설명합니다.
✨ 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 feature/google-places-images

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: 6

🤖 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 `@api/google-places/photo.ts`:
- Around line 13-20: Update the photo lookup caching and rate-limiting flow
around responseCache and rateLimitEntries to key requests only by normalized
name, address, latitude, and longitude, excluding unused request-body fields.
Add TTL-based removal of expired entries and enforce a maximum entry count with
eviction for responseCache; apply the same expired-entry cleanup to
rateLimitEntries.

In `@api/google-places/placePhoto.ts`:
- Around line 33-43: Update lookupPlacePhoto to accept unknown input and
validate that requestBody is a non-null object before accessing its properties;
throw InvalidPlacePhotoRequestError for invalid values such as JSON null so both
photo.ts and vite.config.ts return 400 instead of 502.
- Around line 60-111: Apply a shared timeout-enabled fetchGooglePlaces helper to
fetchTextSearch, fetchPlaceDetails, and fetchMedia in placePhoto.ts, using
AbortSignal.timeout for every upstream request. Handle DOMException errors with
name AbortError separately from other failures, preserving existing error
behavior for non-timeout failures. Apply the same pattern to the corresponding
Google Places calls in placeHours.ts.

In `@src/pages/admin/course-registration/place-selection/index.tsx`:
- Around line 36-79: Update getCategoryFallbackIcon to accept a scale parameter
and set every fallback icon’s width and height to 28 * scale, removing the fixed
h-7 w-7 sizing while preserving the shared styling. Update every call site of
getCategoryFallbackIcon to pass the active scale value, with no artificial
minimum bound.

In `@src/pages/admin/course-registration/visit-order/uploadAdminCourseImages.ts`:
- Around line 33-45: Remove the cross-origin fetch logic from fetchImageAsFile
and stop converting the Google Places photoUri into a browser-side File. Pass
photoUri only as an <img> src, or route downloading through a same-origin
backend proxy before creating the upload File, while keeping API keys out of the
client.

In `@src/pages/local-recommendation/components/SelectionResultCard.tsx`:
- Around line 61-64: Update the imageSize calculation in SelectionResultCard so
the add-action thumbnail also multiplies ADD_IMAGE_SIZE by scale, matching the
scaled remove-action thumbnail and other UI dimensions.
🪄 Autofix

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: 2f172a1a-a582-4f9d-9151-8fbd86ce4ef5

📥 Commits

Reviewing files that changed from the base of the PR and between 8f46b7a and 4386dff.

📒 Files selected for processing (24)
  • api/google-places/parseRequest.ts
  • api/google-places/photo.ts
  • api/google-places/placeHours.ts
  • api/google-places/placePhoto.ts
  • src/apis/courses.ts
  • src/apis/googlePlacesPhoto.ts
  • src/apis/localRecommendations.ts
  • src/hooks/usePlaceOpeningHours.ts
  • src/hooks/usePlacePhotos.ts
  • src/hooks/utils/createRequestQueue.ts
  • src/pages/admin/course-registration/place-selection/index.tsx
  • src/pages/admin/course-registration/visit-order/uploadAdminCourseImages.ts
  • src/pages/admin/course-registration/visit-order/useAdminCourseVisitOrder.ts
  • src/pages/local-recommendation/components/PlaceMarkerIcon.tsx
  • src/pages/local-recommendation/components/SelectionResultCard.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModalFooter.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/hooks/useSelectedPlaces.ts
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/store/localRecommendation.store.ts
  • vercel.json
  • vite.config.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (5)
📚 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/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModalFooter.tsx
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/components/PlaceMarkerIcon.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/components/SelectionResultCard.tsx
  • src/pages/admin/course-registration/place-selection/index.tsx
📚 Learning: 2026-08-03T09:41:11.931Z
Learnt from: KJun-2
Repo: yeogido/frontend PR: 188
File: src/pages/detail/components/CourseDetailLayout.tsx:202-205
Timestamp: 2026-08-03T09:41:11.931Z
Learning: When implementing the shared back-navigation UI component in yeogido/frontend, manage all UI dimensions, including positional offsets, according to the project’s global `scale` policy so the component remains visually consistent across screens.

Applied to files:

  • src/pages/local-recommendation/place-selection/components/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModalFooter.tsx
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/components/PlaceMarkerIcon.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/components/SelectionResultCard.tsx
  • src/pages/admin/course-registration/place-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/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModalFooter.tsx
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/components/PlaceMarkerIcon.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/components/SelectionResultCard.tsx
  • src/pages/admin/course-registration/place-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/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModalFooter.tsx
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/components/PlaceMarkerIcon.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/components/SelectionResultCard.tsx
  • src/pages/admin/course-registration/place-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/SelectedPlaceSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlaceSearchSection.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModalFooter.tsx
  • src/pages/local-recommendation/place-selection/index.tsx
  • src/pages/local-recommendation/components/PlaceMarkerIcon.tsx
  • src/pages/local-recommendation/place-selection/components/PlacePhotoModal.tsx
  • src/pages/local-recommendation/components/SelectionResultCard.tsx
  • src/pages/admin/course-registration/place-selection/index.tsx
🔇 Additional comments (22)
src/apis/courses.ts (1)

79-79: LGTM!

Also applies to: 102-102

src/apis/localRecommendations.ts (1)

1-2: LGTM!

Also applies to: 62-62

src/pages/admin/course-registration/place-selection/index.tsx (2)

3-22: LGTM!

Also applies to: 95-111, 184-184


262-262: LGTM!

src/pages/admin/course-registration/visit-order/useAdminCourseVisitOrder.ts (1)

29-32: LGTM!

Also applies to: 115-144

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

1-21: LGTM!

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

19-20: LGTM!

Also applies to: 29-29, 63-74

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

10-24: LGTM!

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

46-48: LGTM!

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

38-38: LGTM!

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

42-42: LGTM!

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

32-32: LGTM!

Also applies to: 180-181

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

38-40: LGTM!

Also applies to: 76-87

api/google-places/parseRequest.ts (1)

1-9: LGTM!

api/google-places/photo.ts (1)

22-68: LGTM!

vite.config.ts (1)

10-20: LGTM!

Also applies to: 30-75, 132-136

vercel.json (1)

11-14: LGTM!

api/google-places/placeHours.ts (1)

1-1: LGTM!

src/hooks/utils/createRequestQueue.ts (1)

1-24: LGTM!

src/hooks/usePlacePhotos.ts (1)

1-57: LGTM!

src/hooks/usePlaceOpeningHours.ts (1)

8-8: LGTM!

Also applies to: 17-17, 89-89

src/apis/googlePlacesPhoto.ts (1)

1-27: LGTM!

Comment thread api/google-places/photo.ts
Comment thread api/google-places/placePhoto.ts
Comment thread api/google-places/placePhoto.ts Outdated
Comment thread src/pages/admin/course-registration/place-selection/index.tsx Outdated
Comment thread src/pages/local-recommendation/components/SelectionResultCard.tsx
…ature/google-places-images

# Conflicts:
#	src/apis/courses.ts
#	src/pages/admin/course-registration/visit-order/useAdminCourseVisitOrder.ts

@junehuk junehuk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Image 확인했습니다

@junehuk
junehuk merged commit e6d96ea into develop Aug 9, 2026
3 checks passed
kimtaehyeokkkk added a commit that referenced this pull request Aug 9, 2026
구글 플레이스 이미지 연동(#301)이 develop에 먼저 머지되면서 겹친
useSelectedPlaces.ts 충돌만 해결 — addSelectedPlace의 imageKey/imageUrl
null 초기화(우리동네 코스 수정 기능)는 유지.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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] 구글 플레이스 이미지 연동

2 participants