Skip to content

[Feat/#83] 리스트-맵 매장 관련 API 연동#84

Merged
skyblue1232 merged 5 commits intodevelopfrom
feat/#83/list-map-apis
Apr 4, 2026
Merged

[Feat/#83] 리스트-맵 매장 관련 API 연동#84
skyblue1232 merged 5 commits intodevelopfrom
feat/#83/list-map-apis

Conversation

@skyblue1232
Copy link
Copy Markdown
Contributor

@skyblue1232 skyblue1232 commented Apr 4, 2026

✅ 작업 내용

📝 Description

  • 메인/지도/상세 흐름에 필요한 Store 관련 API 연동
  • 카카오 지도 바텀시트 UI를 서버 응답 구조에 맞게 수정
  • 가게 주소 및 영업시간 표시 방식 개선

작업한 내용을 체크해주세요.

  • Store 리스트 조회 API 연동 (위치 기반)
  • 태그 기반 Store 조회 API 연동
  • 주소 기반 Store 조회 API 연동
  • Store 단건 조회 API 연동 (바텀시트)
  • Store 상세 조회 API 연동 (/stores/{storeId})
  • 바텀시트 props 구조 변경 (address → roadAddress, jibunAddress)
  • businessHours 처리 로직 수정 (객체 → 문자열)
  • KakaoMap ↔ BottomSheet 데이터 흐름 정리

🚀 설계 의도 및 개선점

  • 서버 API 응답 구조를 그대로 사용하도록 프론트 로직 단순화
  • 지도 → 바텀시트 → 상세 페이지로 이어지는 흐름을 API 기준으로 재정렬
  • 주소/영업시간 처리 로직을 분리하여 재사용성과 가독성 개선

📎 기타 참고사항

  • businessHours는 오늘 기준 시간만 추출하여 문자열로 전달

Fixes #83

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 카카오맵을 활용한 주소 검색 기능 추가
    • 선택한 주소 기반 매장 필터링 및 지도 표시
    • 매장 카드에 이미지, 이메일, 영업시간 정보 표시
    • 개선된 매장 상세 페이지
  • Style

    • UI 오버레이 배경 색상 및 헤더 디자인 개선

@skyblue1232 skyblue1232 self-assigned this Apr 4, 2026
@skyblue1232 skyblue1232 added feat 기능 구현 및 생성 chore 자잘한 수정 api 서버 - 클라이언트 통신 labels Apr 4, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 4, 2026

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

Project Deployment Actions Updated (UTC)
compasser-customer Error Error Apr 4, 2026 2:06pm
compasser-owner Ready Ready Preview, Comment Apr 4, 2026 2:06pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1c2d32a5-f2ea-4d11-ad14-14971084c274

📥 Commits

Reviewing files that changed from the base of the PR and between 25f4d4f and cedce21.

📒 Files selected for processing (1)
  • packages/api/src/models/store.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/models/store.ts

📝 Walkthrough

Walkthrough

Kakao 주소 검색 통합과 지도/리스트를 모의 데이터에서 API 기반 실시간 페칭으로 전환합니다. 주소 검색 UI, 다수의 React Query 훅, 지도 마커/바텀시트 연동, 상세 조회 훅 및 타입·모델 확장이 포함됩니다.

Changes

Cohort / File(s) Summary
주소 검색 & 타입
apps/customer/src/app/(tabs)/main/_apis/searchAddressByKakao.ts, apps/customer/src/app/(tabs)/main/_types/address-search.ts, apps/customer/src/shared/types/kakao.d.ts
Kakao Geocoder를 호출하는 클라이언트 함수 추가, AddressSearchItem 타입 추가 및 전역 Kakao 타입 선언 파일 추가. 입력 검증·callback 상태 처리 포함.
주소 검색 UI
apps/customer/src/app/(tabs)/main/_components/AddressSearchBottomSheet.tsx
BottomSheet 기반 주소 검색 컴포넌트 추가(250ms 디바운스), 검색 상태/결과 관리 및 선택 콜백 제공.
페이지 상태 및 데이터 흐름
apps/customer/src/app/(tabs)/main/_components/MainPageClient.tsx
선택 주소/현재 위치에 따른 좌표수집, useStores / useStoresByAddress 조건부 사용, 클라이언트 필터·정렬 상태 및 AddressSearchBottomSheet 연동 추가.
맵·리스트 컴포넌트 변경
apps/customer/src/app/(tabs)/main/_components/KakaoMap.tsx, apps/customer/src/app/(tabs)/main/_components/MainMapView.tsx, apps/customer/src/app/(tabs)/main/_components/MainListView.tsx
컴포넌트를 props 기반으로 전환하여 storesselectedAddress 주입. 마커 생성 로직, 선택 마커 관리, SDK URL에 libraries=services 추가. 리스트는 API DTO 사용으로 변경.
스토어 카드·바텀시트 수정
apps/customer/src/app/(tabs)/main/_components/MainStoreCard.tsx, apps/customer/src/app/(tabs)/main/_components/MapStoreBottomSheet.tsx
GetStoreReqDTO 기반 렌더링으로 전환. 썸네일·태그·영업시간 표현 변경, 바텀시트에 도로명/지번 주소 분리 및 이메일/영업시간 optional 처리.
상세 페이지 API 전환
apps/customer/src/app/(tabs)/main/store/[id]/page.tsx, apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx, apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts
페이지가 storeId 기반으로 변경, useStoreDetail 훅 사용으로 상세 데이터 페칭. 기존 모의 타입/인터페이스 제거 및 businessHours·이미지·randomBoxes 매핑 변경.
Store 쿼리 훅들
apps/customer/src/shared/queries/query/store/useStores.ts, .../useStoresByAddress.ts, .../useStoreDetail.ts, .../useStoreSimple.ts
TanStack React Query 래퍼 훅 4개 추가(좌표 기반, 주소 기반, 상세, 간단 조회). enabled 플래그로 조건부 실행 처리.
API 모델·모듈 확장
packages/api/src/models/store.ts, apps/customer/src/shared/api/api.ts
Store DTO 확장(이미지, randomBoxes, storeEmail 등) 및 storeModule 생성/내보내기 추가. StoreByAddressResponse 제네릭 수정.
디자인 시스템·유틸·테마
packages/design-system/src/components/Header/Header.tsx, .../Header.types.ts, .../BottomSheet/BottomSheet.tsx, .../libs/utils.ts, packages/tailwind-config/theme.css
Header location-search에 onInputClick 추가, BottomSheet 오버레이 색상 변경(bg-default/50), Tailwind 유틸·merge 확장(border-secondary*).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant MainPageClient as MainPageClient<br/>(Client)
    participant AddressSheet as AddressSearchBottomSheet<br/>(UI)
    participant KakaoAPI as Kakao<br/>Geocoder
    participant QueryHooks as Store Query Hooks
    participant KakaoMap as KakaoMap<br/>Component
    participant MainList as MainListView<br/>Component

    User->>MainPageClient: 앱 로드 / 위치 권한 수락
    MainPageClient->>QueryHooks: useStores(lat, lon) (기본 호출)
    QueryHooks-->>MainPageClient: 근처 가게 목록 반환

    User->>AddressSheet: 주소 입력
    AddressSheet->>KakaoAPI: searchAddressByKakao(keyword)
    KakaoAPI-->>AddressSheet: 주소 결과
    User->>AddressSheet: 주소 선택
    AddressSheet->>MainPageClient: onSelectAddress(item)
    MainPageClient->>QueryHooks: useStoresByAddress(address) (전환)
    QueryHooks-->>MainPageClient: 주소 기반 가게 목록

    MainPageClient->>KakaoMap: selectedAddress + filteredStores
    KakaoMap->>KakaoMap: 선택 마커/모든 마커 업데이트

    MainPageClient->>MainList: stores + filters
    MainList-->>User: 리스트 렌더링
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50분

Possibly related PRs

Poem

🐰
지도 위로 폴짝 뛰어가니, 주소가 빙글빙글
카카오에게 묻고, 가게들이 줄지어 와요
버튼 한 번에 바텀시트가 활짝 열리고
실시간 데이터에 내 귀도 쫑긋하네
당근 대신 API로 맛난 길을 찾았네 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 주요 변경 사항인 리스트-맵 매장 관련 API 연동을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed PR의 코드 변경 사항이 #83의 모든 주요 요구사항을 충족합니다: 카카오 주소 검색, 위치/주소 기반 API 연동, 지도 마커 렌더링, 바텀시트 UI, businessHours 문자열 처리, 상세 페이지 라우팅.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 #83의 리스트-맵 API 연동 범위 내에 있으며, 관련된 타입 정의, 쿼리 훅, UI 컴포넌트, Kakao 타입 선언, 디자인 시스템 지원 변경만 포함됩니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#83/list-map-apis

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
Copy Markdown

@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: 12

Caution

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

⚠️ Outside diff range comments (1)
apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts (1)

1-8: ⚠️ Potential issue | 🔴 Critical

제거된 타입으로 인해 TypeScript 컴파일이 실패합니다.

store-detail.ts에서 DayKey만 export하고 있으나, 아래 6개 파일에서 존재하지 않는 StoreBusinessHour, StoreMenuItem, StoreDetailItem을 import하고 있습니다:

  • apps/customer/src/app/(tabs)/main/store/_constants/mockStoreDetail.ts
  • apps/customer/src/app/(tabs)/main/store/_components/StoreMenuCard.tsx
  • apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx
  • apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseContent.tsx
  • apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseOrderCard.tsx
  • apps/customer/src/app/(tabs)/main/store/[id]/purchase/_components/PurchaseCompleteModal.tsx

이들 타입을 store-detail.ts에 추가하거나, 다른 파일에서 재정의한 후 import 경로를 수정해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/store/_types/store-detail.ts around lines
1 - 8, The build fails because only DayKey is exported from store-detail.ts
while consumers expect types StoreBusinessHour, StoreMenuItem, and
StoreDetailItem; restore (or add) those missing type definitions and export them
from store-detail.ts with the exact names imported by StoreMenuCard,
StoreDetailContent, PurchaseContent, PurchaseOrderCard, and
PurchaseCompleteModal, or alternatively define matching types in those files and
update their import paths to the new location; ensure the shapes (fields) match
how those components use the types and export them alongside DayKey.
🧹 Nitpick comments (6)
apps/customer/src/app/(tabs)/main/_apis/searchAddressByKakao.ts (1)

52-53: ID 생성 방식 검토.

id: \${item.x}-${item.y}-${index}`로 ID를 생성하고 있습니다. 동일한 좌표를 가진 결과가 다른 검색에서 반환될 경우 index가 다르면 다른 ID가 생성됩니다. 현재 사용 맥락에서는 문제가 없어 보이지만, ID의 일관성이 필요한 경우 item.address_name`을 포함하는 것을 고려해 보세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_apis/searchAddressByKakao.ts around lines
52 - 53, The current ID construction in the mapping (id:
`${item.x}-${item.y}-${index}`) can vary across searches for identical
coordinates; update the ID generation inside the function that maps Kakao
results (the mapping callback where item is used) to include a stable field such
as item.address_name (e.g., combine item.x, item.y and item.address_name)
instead of the index so IDs remain consistent across searches; ensure you
normalize or trim item.address_name if necessary to avoid unsafe characters.
apps/customer/src/app/(tabs)/main/_components/KakaoMap.tsx (1)

143-152: 중복 헬퍼 함수 통합을 권장합니다.

  • mapServerTagToMainCategory: MainPageClient.tsx의 동일 함수와 중복
  • readTodayBusinessHours: MainStoreCard.tsxformatBusinessHours, StoreDetailContent.tsx의 business hours 로직과 유사

공통 유틸리티 모듈로 추출하여 일관성과 유지보수성을 높이는 것을 권장합니다.

Also applies to: 158-184

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/KakaoMap.tsx around lines 143
- 152, The code duplicates helper logic (e.g., mapServerTagToMainCategory in
KakaoMap.tsx and MainPageClient.tsx, and business-hours logic like
readTodayBusinessHours / formatBusinessHours used in MainStoreCard.tsx and
StoreDetailContent.tsx); extract these helpers into a shared utility module
(e.g., storeUtils or businessHoursUtils), implement and export functions such as
mapServerTagToMainCategory and readTodayBusinessHours there, update
KakaoMap.tsx, MainPageClient.tsx, MainStoreCard.tsx, and StoreDetailContent.tsx
to import and use the shared utilities, and remove the duplicated local
implementations to ensure a single source of truth.
apps/customer/src/app/(tabs)/main/_components/MainListView.tsx (1)

48-54: 로딩 상태와 빈 결과 처리.

로딩 중 상태 표시가 있으나, 로딩 완료 후 stores가 빈 배열일 경우에 대한 빈 상태(empty state) UI가 없습니다. 사용자 경험 개선을 위해 검색 결과가 없을 때 안내 메시지 표시를 고려해 보세요.

💡 빈 상태 UI 추가 제안
 {isLoading ? (
   <div className="body2-r text-gray-600">로딩중...</div>
+) : stores.length === 0 ? (
+  <div className="body2-r text-gray-600">검색 결과가 없습니다.</div>
 ) : (
   stores.map((item) => (
     <MainStoreCard key={item.storeId} item={item} />
   ))
 )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/MainListView.tsx around lines
48 - 54, The component rendering in MainListView currently shows a loading
indicator when isLoading is true and otherwise maps stores to MainStoreCard, but
it lacks an empty-state UI when stores is an empty array; update the render
logic in MainListView (the conditional using isLoading, stores, and
MainStoreCard) to check for stores.length === 0 after loading completes and
render a user-friendly empty message/component (e.g., a div with guidance or a
dedicated EmptyState component) instead of rendering nothing; ensure the key
path still uses item.storeId and preserve existing loading behavior.
apps/customer/src/app/(tabs)/main/_components/MainStoreCard.tsx (2)

26-35: 이미지 렌더링 개선이 적절합니다.

storeImage 존재 여부에 따른 조건부 렌더링과 Gift 아이콘 폴백 처리가 잘 구현되어 있습니다. 다만, Next.js 프로젝트에서 외부 이미지를 렌더링할 때는 next/imageImage 컴포넌트 사용을 고려해 보세요. 이미지 최적화와 레이아웃 시프트 방지에 도움이 됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/MainStoreCard.tsx around lines
26 - 35, MainStoreCard currently uses a plain <img> tag to render
item.storeImage; in a Next.js app replace this with next/image's Image component
(import Image from 'next/image') inside the same conditional so you still render
Icon as a fallback when item.storeImage is falsey, provide explicit width and
height (or use fill with parent position:relative) to avoid layout shift, set
objectFit via style/className to maintain cover behavior, and ensure external
image domains are allowed in next.config.js if the URLs are remote; keep
alt={item.storeName} and preserve the surrounding container and accessibility
attributes.

72-102: 영업시간 포맷 로직이 여러 파일에 중복됩니다.

formatBusinessHours 함수가 KakaoMap.tsxreadTodayBusinessHours, StoreDetailContent.tsxnormalizeBusinessHours와 유사한 로직을 구현하고 있습니다. 공통 유틸리티 함수로 추출하여 재사용성을 높이는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/MainStoreCard.tsx around lines
72 - 102, Extract the duplicated business-hour parsing logic into a single
shared utility (e.g., create a function like formatBusinessHours or
getTodayBusinessHours in a common utils module) and replace the similar
implementations in formatBusinessHours, readTodayBusinessHours, and
normalizeBusinessHours with calls to that utility; ensure the new utility
accepts the same input shape (raw?: unknown or Record<string,string>), handles
invalid input by returning the "영업시간 정보 없음" fallback, maps weekday strings
(Mon..Sun) to keys (mon..sun) using the same Asia/Seoul timeZone logic, and
returns the same output string format (e.g., "영업중 {hours}") so existing
consumers need only swap their local logic for the shared call.
apps/customer/src/app/(tabs)/main/_components/MainPageClient.tsx (1)

109-111: "추천순" 정렬 로직 확인이 필요합니다.

현재 "추천순"이 storeId 내림차순으로 구현되어 있습니다. 이는 실제 추천 로직이 아닌 임시 구현으로 보입니다. 추후 실제 추천 알고리즘이나 서버 기반 정렬이 필요한 경우 변경이 필요할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/MainPageClient.tsx around
lines 109 - 111, The "추천순" branch in MainPageClient.tsx currently sorts by
storeId (items.sort((a, b) => b.storeId - a.storeId)), which is a placeholder
and incorrect; update the selectedSort === "추천순" handling to use a real
recommendation metric (e.g., a recommendationScore field on item) or call the
server API to return pre-sorted results instead of using storeId, and if that
field/API is not yet available add a clear TODO comment and fall back to a
stable default (e.g., no-op or sort by createdAt/popularity) in the sort branch
(refer to selectedSort check and the items.sort call in MainPageClient.tsx).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/customer/src/app/`(tabs)/main/_components/AddressSearchBottomSheet.tsx:
- Around line 50-60: The async search block can be overwritten by out-of-order
responses; add a request identifier or cancellation flag to ensure only the
latest request updates state: create a ref like latestRequestIdRef and increment
it before calling searchAddressByKakao (or create an AbortController if
searchAddressByKakao supports aborting) then capture the current id in the async
callback and, before calling setResults/setIsSearching, verify the id matches
latestRequestIdRef and that the sheet is still open (use an isOpen or isMounted
ref); also ensure any previous pending request is aborted or ignored when the
sheet closes. Use the existing debounceRef and
trimmedKeyword/searchAddressByKakao/setResults/setIsSearching identifiers to
locate where to add this logic.

In `@apps/customer/src/app/`(tabs)/main/_components/KakaoMap.tsx:
- Around line 154-156: The function readStoreAddress is defined but never used;
remove the unused function declaration (readStoreAddress) from the KakaoMap
component file to eliminate dead code and keep the module clean, or if it is
intended for future use, add a clear TODO and export/usage where applicable
(e.g., replace inline address logic with a call to readStoreAddress in the
rendering or helper that builds store addresses).

In `@apps/customer/src/app/`(tabs)/main/_components/MainPageClient.tsx:
- Around line 158-167: The function mapServerTagToMainCategory has indentation
issues and is duplicated in KakaoMap.tsx; fix the formatting in this file
(correct function indentation and ensure exhaustive switch handling) and extract
the function into a shared utility (e.g., create and export
mapServerTagToMainCategory from a new/common util module) then import and use
that exported function in both MainPageClient.tsx and KakaoMap.tsx, keeping the
same type signatures (StoreTag and MainCategory) and updating exports/imports
accordingly.

In `@apps/customer/src/app/`(tabs)/main/_components/MainStoreCard.tsx:
- Around line 61-70: The function mapServerTagToLabel has inconsistent
indentation violating the project's 2-space style; reformat the function body
(including the switch, case labels "CAFE", "BAKERY", "RESTAURANT", and their
return lines) to use 2-space indentation throughout to satisfy ESLint rules for
apps/customer/src/app/(tabs)/main/_components/MainStoreCard.tsx and ensure no
other spacing errors remain in mapServerTagToLabel.

In `@apps/customer/src/app/`(tabs)/main/_components/MapStoreBottomSheet.tsx:
- Around line 76-78: The span in MapStoreBottomSheet currently renders {email ??
"이메일 정보 없음"} which only handles null/undefined; update the conditional to also
treat empty-string (and optionally whitespace) as missing by checking email (or
email?.trim()) and falling back to "이메일 정보 없음" so empty values won't render
blank text; change the expression around the span (the email variable usage) to
use this combined check.
- Around line 88-90: In MapStoreBottomSheet.tsx update the UI text that
currently forces "영업중" when businessHours exists: instead of unconditionally
showing "영업중 {businessHours}" in the span, either compute real open/closed state
(using the component's business hours parsing logic) and render "영업중" only when
currently open, or—if you do not implement state calculation now—change the copy
to a neutral label like "영업시간 {businessHours}" so the UI does not mislead;
locate the span rendering in MapStoreBottomSheet and adjust the displayed string
accordingly.

In `@apps/customer/src/app/`(tabs)/main/store/_components/StoreDetailContent.tsx:
- Around line 224-237: The StoreMenuCard is missing required fields when mapping
RandomBoxRespDTO → StoreMenuItem in StoreDetailContent: include remainingCount,
pickupStartTime, and pickupEndTime in the object passed to StoreMenuCard inside
the menus.map so StoreMenuCard doesn't render "undefined"; use the corresponding
RandomBoxRespDTO properties (e.g., menu.remainingCount, menu.pickupStartTime,
menu.pickupEndTime) or provide sensible fallbacks (0 for remainingCount and
empty string or formatted defaults for pickupStartTime/pickupEndTime) when those
fields are absent, and adjust the StoreMenuItem typing if needed so
StoreMenuCard and RandomBoxRespDTO align.
- Around line 255-263: The readJibunAddress function contains unnecessary
defensive casting and an incorrect typo variant (jibunAddres) that doesn't exist
elsewhere; simplify it by removing the extra type assertions and the fallback to
the misspelled property and return store?.jibunAddress ?? "" directly, keeping
the function signature and using the StoreRespDTO type.

In `@apps/customer/src/app/`(tabs)/main/store/[id]/page.tsx:
- Around line 16-20: The current check using Number.isNaN allows invalid values
like -1, 1.5, or Infinity to pass; update the validation around storeId (the
value passed into StoreDetailContent) to ensure it's a finite positive integer
by verifying Number.isFinite(storeId) && Number.isInteger(storeId) && storeId >
0, and call notFound() when that condition fails instead of only checking
Number.isNaN; adjust the logic where storeId is derived/parsed so the same
validation is applied before rendering StoreDetailContent.

In `@apps/customer/src/shared/types/kakao.d.ts`:
- Around line 87-103: 빈 인터페이스 선언(KakaoLatLng, KakaoMarkerImage, KakaoSize,
KakaoPoint)을 타입 별칭으로 바꿔주세요: 각 interface 대신 "type <Name> = object" 또는 "type
<Name> = unknown" 형태로 선언하고 기존 참조(예: KakaoMapInstance.setCenter,
KakaoMarker.setPosition 등)는 그대로 유지해서 타입 호환이 되도록 합니다; 빈 interface 선언문은 제거하고 필요하면
린트 규칙 비활성화 대신 타입 별칭을 사용하세요.

In `@packages/api/src/models/store.ts`:
- Around line 23-32: RandomBoxRespDTO is missing fields (remainingCount,
pickupStartTime, pickupEndTime) that StoreMenuCard and StoreDetailContent.tsx
expect, and StoreMenuItem is not defined in store-detail.ts despite being
imported elsewhere; fix by updating the RandomBoxRespDTO interface to include
remainingCount:number, pickupStartTime:string|Date, pickupEndTime:string|Date
(or nullable types matching API), or create a conversion function that maps
RandomBoxRespDTO -> StoreMenuItem populating those fields, and add a proper
StoreMenuItem type definition in store-detail.ts (or a shared types file) that
matches StoreMenuCard's props so all components import the correct type; ensure
StoreDetailContent.tsx uses the converter or the extended DTO to avoid passing
undefined values at runtime.

In `@packages/design-system/src/components/Header/Header.tsx`:
- Line 43: The input currently only handles mouse interaction via onInputClick;
add a keyboard handler so read-only inputs acting as selection triggers are
usable via keyboard: add an onKeyDown on the same input that checks for Enter or
Space when inputReadOnly is true, preventDefault for Space, and invoke
onInputClick (casting the KeyboardEvent to the expected event type or adapting
the handler call) so keyboard users can activate the control; reference the
inputReadOnly prop and the onInputClick handler in Header.tsx and ensure the new
onKeyDown logic coexists with the existing onClick.

---

Outside diff comments:
In `@apps/customer/src/app/`(tabs)/main/store/_types/store-detail.ts:
- Around line 1-8: The build fails because only DayKey is exported from
store-detail.ts while consumers expect types StoreBusinessHour, StoreMenuItem,
and StoreDetailItem; restore (or add) those missing type definitions and export
them from store-detail.ts with the exact names imported by StoreMenuCard,
StoreDetailContent, PurchaseContent, PurchaseOrderCard, and
PurchaseCompleteModal, or alternatively define matching types in those files and
update their import paths to the new location; ensure the shapes (fields) match
how those components use the types and export them alongside DayKey.

---

Nitpick comments:
In `@apps/customer/src/app/`(tabs)/main/_apis/searchAddressByKakao.ts:
- Around line 52-53: The current ID construction in the mapping (id:
`${item.x}-${item.y}-${index}`) can vary across searches for identical
coordinates; update the ID generation inside the function that maps Kakao
results (the mapping callback where item is used) to include a stable field such
as item.address_name (e.g., combine item.x, item.y and item.address_name)
instead of the index so IDs remain consistent across searches; ensure you
normalize or trim item.address_name if necessary to avoid unsafe characters.

In `@apps/customer/src/app/`(tabs)/main/_components/KakaoMap.tsx:
- Around line 143-152: The code duplicates helper logic (e.g.,
mapServerTagToMainCategory in KakaoMap.tsx and MainPageClient.tsx, and
business-hours logic like readTodayBusinessHours / formatBusinessHours used in
MainStoreCard.tsx and StoreDetailContent.tsx); extract these helpers into a
shared utility module (e.g., storeUtils or businessHoursUtils), implement and
export functions such as mapServerTagToMainCategory and readTodayBusinessHours
there, update KakaoMap.tsx, MainPageClient.tsx, MainStoreCard.tsx, and
StoreDetailContent.tsx to import and use the shared utilities, and remove the
duplicated local implementations to ensure a single source of truth.

In `@apps/customer/src/app/`(tabs)/main/_components/MainListView.tsx:
- Around line 48-54: The component rendering in MainListView currently shows a
loading indicator when isLoading is true and otherwise maps stores to
MainStoreCard, but it lacks an empty-state UI when stores is an empty array;
update the render logic in MainListView (the conditional using isLoading,
stores, and MainStoreCard) to check for stores.length === 0 after loading
completes and render a user-friendly empty message/component (e.g., a div with
guidance or a dedicated EmptyState component) instead of rendering nothing;
ensure the key path still uses item.storeId and preserve existing loading
behavior.

In `@apps/customer/src/app/`(tabs)/main/_components/MainPageClient.tsx:
- Around line 109-111: The "추천순" branch in MainPageClient.tsx currently sorts by
storeId (items.sort((a, b) => b.storeId - a.storeId)), which is a placeholder
and incorrect; update the selectedSort === "추천순" handling to use a real
recommendation metric (e.g., a recommendationScore field on item) or call the
server API to return pre-sorted results instead of using storeId, and if that
field/API is not yet available add a clear TODO comment and fall back to a
stable default (e.g., no-op or sort by createdAt/popularity) in the sort branch
(refer to selectedSort check and the items.sort call in MainPageClient.tsx).

In `@apps/customer/src/app/`(tabs)/main/_components/MainStoreCard.tsx:
- Around line 26-35: MainStoreCard currently uses a plain <img> tag to render
item.storeImage; in a Next.js app replace this with next/image's Image component
(import Image from 'next/image') inside the same conditional so you still render
Icon as a fallback when item.storeImage is falsey, provide explicit width and
height (or use fill with parent position:relative) to avoid layout shift, set
objectFit via style/className to maintain cover behavior, and ensure external
image domains are allowed in next.config.js if the URLs are remote; keep
alt={item.storeName} and preserve the surrounding container and accessibility
attributes.
- Around line 72-102: Extract the duplicated business-hour parsing logic into a
single shared utility (e.g., create a function like formatBusinessHours or
getTodayBusinessHours in a common utils module) and replace the similar
implementations in formatBusinessHours, readTodayBusinessHours, and
normalizeBusinessHours with calls to that utility; ensure the new utility
accepts the same input shape (raw?: unknown or Record<string,string>), handles
invalid input by returning the "영업시간 정보 없음" fallback, maps weekday strings
(Mon..Sun) to keys (mon..sun) using the same Asia/Seoul timeZone logic, and
returns the same output string format (e.g., "영업중 {hours}") so existing
consumers need only swap their local logic for the shared call.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7733de87-779c-49c4-93e8-fe3c1cfdccce

📥 Commits

Reviewing files that changed from the base of the PR and between 108b1bf and 25f4d4f.

⛔ Files ignored due to path filters (3)
  • packages/design-system/src/icons/generated/iconNames.ts is excluded by !**/generated/**
  • packages/design-system/src/icons/generated/spriteSymbols.ts is excluded by !**/generated/**
  • packages/design-system/src/icons/source/Pin.svg is excluded by !**/*.svg
📒 Files selected for processing (24)
  • apps/customer/src/app/(tabs)/main/_apis/searchAddressByKakao.ts
  • apps/customer/src/app/(tabs)/main/_components/AddressSearchBottomSheet.tsx
  • apps/customer/src/app/(tabs)/main/_components/KakaoMap.tsx
  • apps/customer/src/app/(tabs)/main/_components/MainListView.tsx
  • apps/customer/src/app/(tabs)/main/_components/MainMapView.tsx
  • apps/customer/src/app/(tabs)/main/_components/MainPageClient.tsx
  • apps/customer/src/app/(tabs)/main/_components/MainStoreCard.tsx
  • apps/customer/src/app/(tabs)/main/_components/MapStoreBottomSheet.tsx
  • apps/customer/src/app/(tabs)/main/_types/address-search.ts
  • apps/customer/src/app/(tabs)/main/store/[id]/page.tsx
  • apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx
  • apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts
  • apps/customer/src/shared/api/api.ts
  • apps/customer/src/shared/queries/query/store/useStoreDetail.ts
  • apps/customer/src/shared/queries/query/store/useStoreSimple.ts
  • apps/customer/src/shared/queries/query/store/useStores.ts
  • apps/customer/src/shared/queries/query/store/useStoresByAddress.ts
  • apps/customer/src/shared/types/kakao.d.ts
  • packages/api/src/models/store.ts
  • packages/design-system/src/components/BottomSheet/BottomSheet.tsx
  • packages/design-system/src/components/Header/Header.tsx
  • packages/design-system/src/components/Header/Header.types.ts
  • packages/design-system/src/libs/utils.ts
  • packages/tailwind-config/theme.css

Comment on lines +50 to +60
debounceRef.current = window.setTimeout(async () => {
try {
setIsSearching(true);
const searched = await searchAddressByKakao(trimmedKeyword, 10);
setResults(searched);
} catch (error) {
console.error(error);
setResults([]);
} finally {
setIsSearching(false);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "AddressSearchBottomSheet.tsx" | head -5

Repository: CUK-Compasser/FE

Length of output: 136


🏁 Script executed:

cat -n apps/customer/src/app/\(tabs\)/main/_components/AddressSearchBottomSheet.tsx | sed -n '1,100p'

Repository: CUK-Compasser/FE

Length of output: 3180


🏁 Script executed:

find . -type f -name "*searchAddressByKakao*" -o -path "*_apis*" -name "*search*" | head -10

Repository: CUK-Compasser/FE

Length of output: 125


🏁 Script executed:

cat -n apps/customer/src/app/\(tabs\)/main/_apis/searchAddressByKakao.ts 2>/dev/null | head -50

Repository: CUK-Compasser/FE

Length of output: 1772


🏁 Script executed:

rg "window\.setTimeout.*async" apps/customer/src --type ts --type tsx -A 3 | head -50

Repository: CUK-Compasser/FE

Length of output: 87


🏁 Script executed:

rg "setTimeout.*async" apps/ --type ts --type tsx -c | head -20

Repository: CUK-Compasser/FE

Length of output: 87


🏁 Script executed:

rg "setTimeout.*async" apps/ -A 3 2>/dev/null | head -60

Repository: CUK-Compasser/FE

Length of output: 532


🏁 Script executed:

rg "debounce|setIsSearching" apps/customer/src --max-count 20

Repository: CUK-Compasser/FE

Length of output: 1331


검색 응답 경쟁 상태(race condition) 방어가 필요합니다.

Line 50-60의 비동기 API 호출에서 이전 요청의 응답이 늦게 도착하면 최신 키워드 결과를 덮어쓸 수 있습니다. 사용자가 "A" 검색 후 "B"를 검색할 때 "A"의 응답이 "B"의 응답보다 나중에 도착하면 잘못된 결과가 표시됩니다. 또한 시트를 닫은 직후에도 이전 요청의 응답으로 상태가 갱신될 수 있습니다. 요청 식별자 또는 취소 플래그를 사용하여 최신 요청만 반영해 주세요.

제안 코드
   const debounceRef = useRef<number | null>(null);
+  const requestSeqRef = useRef(0);

   useEffect(() => {
+    let cancelled = false;
     if (!open) {
       return;
     }
@@
-    debounceRef.current = window.setTimeout(async () => {
+    const requestId = ++requestSeqRef.current;
+    debounceRef.current = window.setTimeout(async () => {
       try {
         setIsSearching(true);
         const searched = await searchAddressByKakao(trimmedKeyword, 10);
-        setResults(searched);
+        if (!cancelled && requestId === requestSeqRef.current) {
+          setResults(searched);
+        }
       } catch (error) {
-        console.error(error);
-        setResults([]);
+        if (!cancelled && requestId === requestSeqRef.current) {
+          console.error(error);
+          setResults([]);
+        }
       } finally {
-        setIsSearching(false);
+        if (!cancelled && requestId === requestSeqRef.current) {
+          setIsSearching(false);
+        }
       }
     }, 250);

     return () => {
+      cancelled = true;
       if (debounceRef.current) {
         window.clearTimeout(debounceRef.current);
       }
     };
   }, [keyword, open]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/AddressSearchBottomSheet.tsx
around lines 50 - 60, The async search block can be overwritten by out-of-order
responses; add a request identifier or cancellation flag to ensure only the
latest request updates state: create a ref like latestRequestIdRef and increment
it before calling searchAddressByKakao (or create an AbortController if
searchAddressByKakao supports aborting) then capture the current id in the async
callback and, before calling setResults/setIsSearching, verify the id matches
latestRequestIdRef and that the sheet is still open (use an isOpen or isMounted
ref); also ensure any previous pending request is aborted or ignored when the
sheet closes. Use the existing debounceRef and
trimmedKeyword/searchAddressByKakao/setResults/setIsSearching identifiers to
locate where to add this logic.

Comment on lines +154 to +156
function readStoreAddress(store: SimpleStoreInfoDTO) {
return store.roadAddress || store.jibunAddress || "";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

사용되지 않는 함수입니다.

readStoreAddress 함수가 정의되어 있지만 파일 내 어디에서도 호출되지 않습니다. 불필요한 코드는 제거해 주세요.

🗑️ 제거 제안
-function readStoreAddress(store: SimpleStoreInfoDTO) {
-  return store.roadAddress || store.jibunAddress || "";
-}
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function readStoreAddress(store: SimpleStoreInfoDTO) {
return store.roadAddress || store.jibunAddress || "";
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/KakaoMap.tsx around lines 154
- 156, The function readStoreAddress is defined but never used; remove the
unused function declaration (readStoreAddress) from the KakaoMap component file
to eliminate dead code and keep the module clean, or if it is intended for
future use, add a clear TODO and export/usage where applicable (e.g., replace
inline address logic with a call to readStoreAddress in the rendering or helper
that builds store addresses).

Comment on lines +158 to 167
function mapServerTagToMainCategory(tag: StoreTag): MainCategory {
switch (tag) {
case "CAFE":
return "카페";
case "BAKERY":
return "베이커리";
case "RESTAURANT":
return "식당";
}
} No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

ESLint 들여쓰기 오류 및 함수 중복.

  1. 정적 분석 도구에서 들여쓰기 오류가 감지되었습니다.
  2. mapServerTagToMainCategory 함수가 KakaoMap.tsx에도 동일하게 존재합니다. 공통 유틸리티로 추출을 권장합니다.
🔧 들여쓰기 수정 제안
 function mapServerTagToMainCategory(tag: StoreTag): MainCategory {
   switch (tag) {
-    case "CAFE":
-      return "카페";
-    case "BAKERY":
-      return "베이커리";
-    case "RESTAURANT":
-      return "식당";
+  case "CAFE":
+    return "카페";
+  case "BAKERY":
+    return "베이커리";
+  case "RESTAURANT":
+    return "식당";
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function mapServerTagToMainCategory(tag: StoreTag): MainCategory {
switch (tag) {
case "CAFE":
return "카페";
case "BAKERY":
return "베이커리";
case "RESTAURANT":
return "식당";
}
}
function mapServerTagToMainCategory(tag: StoreTag): MainCategory {
switch (tag) {
case "CAFE":
return "카페";
case "BAKERY":
return "베이커리";
case "RESTAURANT":
return "식당";
}
}
🧰 Tools
🪛 ESLint

[error] 160-160: Expected indentation of 2 spaces but found 4.

(indent)


[error] 161-161: Expected indentation of 4 spaces but found 6.

(indent)


[error] 162-162: Expected indentation of 2 spaces but found 4.

(indent)


[error] 163-163: Expected indentation of 4 spaces but found 6.

(indent)


[error] 164-164: Expected indentation of 2 spaces but found 4.

(indent)


[error] 165-165: Expected indentation of 4 spaces but found 6.

(indent)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/MainPageClient.tsx around
lines 158 - 167, The function mapServerTagToMainCategory has indentation issues
and is duplicated in KakaoMap.tsx; fix the formatting in this file (correct
function indentation and ensure exhaustive switch handling) and extract the
function into a shared utility (e.g., create and export
mapServerTagToMainCategory from a new/common util module) then import and use
that exported function in both MainPageClient.tsx and KakaoMap.tsx, keeping the
same type signatures (StoreTag and MainCategory) and updating exports/imports
accordingly.

Comment on lines +61 to +70
function mapServerTagToLabel(tag: StoreTag) {
switch (tag) {
case "CAFE":
return "카페";
case "BAKERY":
return "베이커리";
case "RESTAURANT":
return "식당";
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

ESLint 들여쓰기 오류 수정이 필요합니다.

정적 분석 도구에서 들여쓰기 오류가 감지되었습니다. 프로젝트 설정에 맞게 2스페이스 들여쓰기로 변경해 주세요.

🔧 들여쓰기 수정 제안
 function mapServerTagToLabel(tag: StoreTag) {
   switch (tag) {
-    case "CAFE":
-      return "카페";
-    case "BAKERY":
-      return "베이커리";
-    case "RESTAURANT":
-      return "식당";
+  case "CAFE":
+    return "카페";
+  case "BAKERY":
+    return "베이커리";
+  case "RESTAURANT":
+    return "식당";
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function mapServerTagToLabel(tag: StoreTag) {
switch (tag) {
case "CAFE":
return "카페";
case "BAKERY":
return "베이커리";
case "RESTAURANT":
return "식당";
}
}
function mapServerTagToLabel(tag: StoreTag) {
switch (tag) {
case "CAFE":
return "카페";
case "BAKERY":
return "베이커리";
case "RESTAURANT":
return "식당";
}
}
🧰 Tools
🪛 ESLint

[error] 63-63: Expected indentation of 2 spaces but found 4.

(indent)


[error] 64-64: Expected indentation of 4 spaces but found 6.

(indent)


[error] 65-65: Expected indentation of 2 spaces but found 4.

(indent)


[error] 66-66: Expected indentation of 4 spaces but found 6.

(indent)


[error] 67-67: Expected indentation of 2 spaces but found 4.

(indent)


[error] 68-68: Expected indentation of 4 spaces but found 6.

(indent)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/MainStoreCard.tsx around lines
61 - 70, The function mapServerTagToLabel has inconsistent indentation violating
the project's 2-space style; reformat the function body (including the switch,
case labels "CAFE", "BAKERY", "RESTAURANT", and their return lines) to use
2-space indentation throughout to satisfy ESLint rules for
apps/customer/src/app/(tabs)/main/_components/MainStoreCard.tsx and ensure no
other spacing errors remain in mapServerTagToLabel.

Comment on lines +76 to +78
<span className="body2-r text-gray-700">
{email ?? "이메일 정보 없음"}
</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

이메일 fallback에 빈 문자열 케이스도 포함해 주세요.

현재는 null/undefined만 대체되어 ""면 빈 텍스트가 노출됩니다.

제안 코드
- {email ?? "이메일 정보 없음"}
+ {email?.trim() ? email : "이메일 정보 없음"}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span className="body2-r text-gray-700">
{email ?? "이메일 정보 없음"}
</span>
<span className="body2-r text-gray-700">
{email?.trim() ? email : "이메일 정보 없음"}
</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/_components/MapStoreBottomSheet.tsx around
lines 76 - 78, The span in MapStoreBottomSheet currently renders {email ?? "이메일
정보 없음"} which only handles null/undefined; update the conditional to also treat
empty-string (and optionally whitespace) as missing by checking email (or
email?.trim()) and falling back to "이메일 정보 없음" so empty values won't render
blank text; change the expression around the span (the email variable usage) to
use this combined check.

Comment on lines +255 to +263
function readJibunAddress(store?: StoreRespDTO) {
if (!store) {
return "";
}

return (store as StoreRespDTO & { jibunAddres?: string }).jibunAddress
?? (store as StoreRespDTO & { jibunAddres?: string }).jibunAddres
?? "";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 백엔드 응답에서 jibunAddres 오타가 실제로 존재하는지 확인
rg -n 'jibunAddres[^s]' --type=ts

Repository: CUK-Compasser/FE

Length of output: 360


🏁 Script executed:

# Find StoreRespDTO definition
rg -n 'type StoreRespDTO|interface StoreRespDTO' --type=ts -A 20

Repository: CUK-Compasser/FE

Length of output: 1282


🏁 Script executed:

# Search for all occurrences of jibunAddres (the typo) across the entire codebase
rg -n 'jibunAddres' --type=ts

Repository: CUK-Compasser/FE

Length of output: 1041


🏁 Script executed:

# Check if there are any API response examples or documentation
fd -t f -e json -e md | xargs grep -l "jibunAddres" 2>/dev/null || echo "No JSON/MD files found with jibunAddres"

Repository: CUK-Compasser/FE

Length of output: 99


🏁 Script executed:

# Search for jibunAddress (correct spelling) to understand its usage
rg -n 'jibunAddress' --type=ts -B 2 -A 2

Repository: CUK-Compasser/FE

Length of output: 3685


불필요한 방어 코딩 제거.

StoreRespDTO에는 jibunAddress만 정의되어 있으며, 코드베이스 전체에서 jibunAddres (오타)는 이 함수 내에서만 나타납니다. 백엔드가 실제로 이 오타를 반환한다는 증거가 없으므로, 불필요한 타입 단언과 방어 코딩을 제거하고 다음과 같이 단순화해야 합니다:

function readJibunAddress(store?: StoreRespDTO) {
  return store?.jibunAddress ?? "";
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/store/_components/StoreDetailContent.tsx
around lines 255 - 263, The readJibunAddress function contains unnecessary
defensive casting and an incorrect typo variant (jibunAddres) that doesn't exist
elsewhere; simplify it by removing the extra type assertions and the fallback to
the misspelled property and return store?.jibunAddress ?? "" directly, keeping
the function signature and using the StoreRespDTO type.

Comment on lines +16 to +20
if (Number.isNaN(storeId)) {
notFound();
}

return <StoreDetailContent store={store} />;
return <StoreDetailContent storeId={storeId} />;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n apps/customer/src/app/\(tabs\)/main/store/\[id\]/page.tsx

Repository: CUK-Compasser/FE

Length of output: 649


🏁 Script executed:

find . -name "*StoreDetailContent*" -type f

Repository: CUK-Compasser/FE

Length of output: 136


🏁 Script executed:

cat -n ./apps/customer/src/app/\(tabs\)/main/store/_components/StoreDetailContent.tsx

Repository: CUK-Compasser/FE

Length of output: 10800


🏁 Script executed:

find . -name "*useStoreDetail*" -type f

Repository: CUK-Compasser/FE

Length of output: 124


🏁 Script executed:

cat -n ./apps/customer/src/shared/queries/query/store/useStoreDetail.ts

Repository: CUK-Compasser/FE

Length of output: 403


🏁 Script executed:

find . -path "*shared/api*" -name "*.ts" -o -name "*.tsx" | head -20

Repository: CUK-Compasser/FE

Length of output: 1511


🏁 Script executed:

find . -type f -path "*shared/api*" -name "*.ts" -o -path "*shared/api*" -name "*.tsx"

Repository: CUK-Compasser/FE

Length of output: 132


🏁 Script executed:

cat -n ./apps/customer/src/shared/api/api.ts | head -150

Repository: CUK-Compasser/FE

Length of output: 1408


URL 파라미터 검증을 강화해야 합니다.

현재 Number.isNaN 체크만으로는 -1, 1.5, Infinity 같은 값이 통과합니다. 라우트 파라미터로 사용되는 storeId는 양의 정수로 제한하세요.

제안 코드
-  if (Number.isNaN(storeId)) {
+  if (!Number.isFinite(storeId) || !Number.isInteger(storeId) || storeId <= 0) {
     notFound();
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Number.isNaN(storeId)) {
notFound();
}
return <StoreDetailContent store={store} />;
return <StoreDetailContent storeId={storeId} />;
if (!Number.isFinite(storeId) || !Number.isInteger(storeId) || storeId <= 0) {
notFound();
}
return <StoreDetailContent storeId={storeId} />;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/app/`(tabs)/main/store/[id]/page.tsx around lines 16 - 20,
The current check using Number.isNaN allows invalid values like -1, 1.5, or
Infinity to pass; update the validation around storeId (the value passed into
StoreDetailContent) to ensure it's a finite positive integer by verifying
Number.isFinite(storeId) && Number.isInteger(storeId) && storeId > 0, and call
notFound() when that condition fails instead of only checking Number.isNaN;
adjust the logic where storeId is derived/parsed so the same validation is
applied before rendering StoreDetailContent.

Comment on lines +87 to +103
interface KakaoLatLng {}

interface KakaoMapInstance {
relayout: () => void;
setCenter: (latlng: KakaoLatLng) => void;
}

interface KakaoMarker {
setMap: (map: KakaoMapInstance | null) => void;
setPosition: (position: KakaoLatLng) => void;
}

interface KakaoMarkerImage {}

interface KakaoSize {}

interface KakaoPoint {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

빈 인터페이스를 타입 별칭으로 변경하세요.

ESLint에서 빈 인터페이스 선언에 대해 경고하고 있습니다. Kakao SDK의 불투명(opaque) 타입을 표현하기 위해 object 타입 별칭을 사용하거나, 린트 규칙을 비활성화하는 것을 권장합니다.

🔧 타입 별칭으로 변경 제안
-interface KakaoLatLng {}
+type KakaoLatLng = object;

 interface KakaoMapInstance {
   relayout: () => void;
   setCenter: (latlng: KakaoLatLng) => void;
 }

 interface KakaoMarker {
   setMap: (map: KakaoMapInstance | null) => void;
   setPosition: (position: KakaoLatLng) => void;
 }

-interface KakaoMarkerImage {}
+type KakaoMarkerImage = object;

-interface KakaoSize {}
+type KakaoSize = object;

-interface KakaoPoint {}
+type KakaoPoint = object;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface KakaoLatLng {}
interface KakaoMapInstance {
relayout: () => void;
setCenter: (latlng: KakaoLatLng) => void;
}
interface KakaoMarker {
setMap: (map: KakaoMapInstance | null) => void;
setPosition: (position: KakaoLatLng) => void;
}
interface KakaoMarkerImage {}
interface KakaoSize {}
interface KakaoPoint {}
type KakaoLatLng = object;
interface KakaoMapInstance {
relayout: () => void;
setCenter: (latlng: KakaoLatLng) => void;
}
interface KakaoMarker {
setMap: (map: KakaoMapInstance | null) => void;
setPosition: (position: KakaoLatLng) => void;
}
type KakaoMarkerImage = object;
type KakaoSize = object;
type KakaoPoint = object;
🧰 Tools
🪛 ESLint

[error] 87-87: An empty interface declaration allows any non-nullish value, including literals like 0 and "".

  • If that's what you want, disable this lint rule with an inline comment or configure the 'allowInterfaces' rule option.
  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.

(@typescript-eslint/no-empty-object-type)


[error] 99-99: An empty interface declaration allows any non-nullish value, including literals like 0 and "".

  • If that's what you want, disable this lint rule with an inline comment or configure the 'allowInterfaces' rule option.
  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.

(@typescript-eslint/no-empty-object-type)


[error] 101-101: An empty interface declaration allows any non-nullish value, including literals like 0 and "".

  • If that's what you want, disable this lint rule with an inline comment or configure the 'allowInterfaces' rule option.
  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.

(@typescript-eslint/no-empty-object-type)


[error] 103-103: An empty interface declaration allows any non-nullish value, including literals like 0 and "".

  • If that's what you want, disable this lint rule with an inline comment or configure the 'allowInterfaces' rule option.
  • If you want a type meaning "any object", you probably want object instead.
  • If you want a type meaning "any value", you probably want unknown instead.

(@typescript-eslint/no-empty-object-type)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer/src/shared/types/kakao.d.ts` around lines 87 - 103, 빈 인터페이스
선언(KakaoLatLng, KakaoMarkerImage, KakaoSize, KakaoPoint)을 타입 별칭으로 바꿔주세요: 각
interface 대신 "type <Name> = object" 또는 "type <Name> = unknown" 형태로 선언하고 기존 참조(예:
KakaoMapInstance.setCenter, KakaoMarker.setPosition 등)는 그대로 유지해서 타입 호환이 되도록 합니다;
빈 interface 선언문은 제거하고 필요하면 린트 규칙 비활성화 대신 타입 별칭을 사용하세요.

Comment on lines +23 to +32
export interface RandomBoxRespDTO {
boxId: number;
storeId: number;
boxName: string;
stock: number;
price: number;
buyLimit: number;
content: string;
saleStatus: string;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# StoreMenuItem 타입 정의 확인
ast-grep --pattern 'interface StoreMenuItem {
  $$$
}'

# StoreMenuCard에서 사용하는 필드 확인
rg -n 'item\.(remainingCount|pickupStartTime|pickupEndTime)' --type=ts

Repository: CUK-Compasser/FE

Length of output: 313


🏁 Script executed:

# Find StoreMenuItem interface definition
rg -n 'interface StoreMenuItem' --type=ts -A 15

# Find where RandomBoxRespDTO is imported/used
rg -n 'RandomBoxRespDTO' --type=ts -B 2 -A 5

# Check StoreDetailContent.tsx for the mapping
fd 'StoreDetailContent' --type=ts && cat -n "apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx"

Repository: CUK-Compasser/FE

Length of output: 6802


🏁 Script executed:

# Search for StoreMenuItem interface definition (corrected syntax)
rg -n 'interface StoreMenuItem' -A 15

# Find StoreDetailContent.tsx
fd 'StoreDetailContent'

# Get complete RandomBoxRespDTO definition
rg -n 'export interface RandomBoxRespDTO' -A 12

Repository: CUK-Compasser/FE

Length of output: 1443


🏁 Script executed:

# Search for StoreMenuItem interface
rg -n 'interface StoreMenuItem' -A 12

# Read StoreDetailContent.tsx to see how it uses RandomBoxRespDTO
cat -n "apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx" | head -100

Repository: CUK-Compasser/FE

Length of output: 3448


🏁 Script executed:

# Continue reading StoreDetailContent.tsx
cat -n "apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx" | tail -n +100

# Search for StoreMenuItem interface definition
rg -n 'interface StoreMenuItem' -A 15

Repository: CUK-Compasser/FE

Length of output: 7419


🏁 Script executed:

# Search for StoreMenuItem interface
rg -n 'type StoreMenuItem|interface StoreMenuItem' -A 15

# Read StoreMenuCard.tsx to see what it expects
cat -n "apps/customer/src/app/(tabs)/main/store/_components/StoreMenuCard.tsx" | head -50

Repository: CUK-Compasser/FE

Length of output: 1826


🏁 Script executed:

# Find the store-detail types file
fd 'store-detail' -path '*_types*'

# Read the file
cat -n "apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts"

Repository: CUK-Compasser/FE

Length of output: 393


🏁 Script executed:

# Search for StoreMenuItem type definition
rg -n 'type StoreMenuItem|interface StoreMenuItem' 

# Check if it's maybe defined in the same directory differently
fd '\.ts' "apps/customer/src/app/(tabs)/main/store/_types/"

# Also check what's in store-detail.ts completely
cat "apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts"

Repository: CUK-Compasser/FE

Length of output: 214


🏁 Script executed:

# Search entire codebase for StoreMenuItem definition
rg -n 'type StoreMenuItem\s*=|interface StoreMenuItem\s*{'

# Also search for just StoreMenuItem as variable/export
rg -n 'StoreMenuItem' 

# Check imports in StoreMenuCard again more carefully
grep -n 'import.*StoreMenuItem' "apps/customer/src/app/(tabs)/main/store/_components/StoreMenuCard.tsx"

Repository: CUK-Compasser/FE

Length of output: 1398


🏁 Script executed:

# Search for where StoreMenuItem might be defined
rg -n 'export type StoreMenuItem|export interface StoreMenuItem'

# Look in all _types files
find . -path '*_types*' -name '*.ts' -o -name '*.tsx'

# Check git history for store-detail.ts to see if StoreMenuItem was removed
git log --oneline "apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts" | head -10

Repository: CUK-Compasser/FE

Length of output: 26765


🏁 Script executed:

# Check if StoreMenuItem exists anywhere in the codebase outside of imports
rg 'export (type|interface) StoreMenuItem' 

# Check git diff for recent changes to store-detail.ts
git log -p -n 3 -- "apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts" | head -100

Repository: CUK-Compasser/FE

Length of output: 42


RandomBoxRespDTO와 StoreMenuItem 간 필드 불일치가 확인되었습니다.

RandomBoxRespDTOremainingCount, pickupStartTime, pickupEndTime 필드를 포함하지 않습니다. StoreMenuCard는 이 세 필드를 사용하여 렌더링하므로, StoreDetailContent.tsx에서 RandomBoxRespDTO를 매핑할 때 undefined 값이 전달되어 런타임 오류가 발생합니다.

더 심각한 문제로, StoreMenuItem 타입이 store-detail.ts에 정의되지 않았는데 여러 컴포넌트에서 임포트되고 있습니다. 필드 누락은 물론, 타입 정의 자체가 결여되어 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/models/store.ts` around lines 23 - 32, RandomBoxRespDTO is
missing fields (remainingCount, pickupStartTime, pickupEndTime) that
StoreMenuCard and StoreDetailContent.tsx expect, and StoreMenuItem is not
defined in store-detail.ts despite being imported elsewhere; fix by updating the
RandomBoxRespDTO interface to include remainingCount:number,
pickupStartTime:string|Date, pickupEndTime:string|Date (or nullable types
matching API), or create a conversion function that maps RandomBoxRespDTO ->
StoreMenuItem populating those fields, and add a proper StoreMenuItem type
definition in store-detail.ts (or a shared types file) that matches
StoreMenuCard's props so all components import the correct type; ensure
StoreDetailContent.tsx uses the converter or the extended DTO to avoid passing
undefined values at runtime.

name={inputName}
value={value}
onChange={onChange}
onClick={onInputClick}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# location-search 헤더 사용처와 입력 속성 확인
rg -n --type=tsx 'variant\s*=\s*["'\'']location-search["'\'']|onInputClick\s*=|inputReadOnly\s*='

Repository: CUK-Compasser/FE

Length of output: 87


🏁 Script executed:

#!/bin/bash
# Header.tsx 파일 확인
cat packages/design-system/src/components/Header/Header.tsx

Repository: CUK-Compasser/FE

Length of output: 3838


🏁 Script executed:

#!/bin/bash
# 수정된 패턴 검색 - 파일 타입 제한 없음
rg -n 'location-search|onInputClick|inputReadOnly' packages/design-system/src/components/Header/

Repository: CUK-Compasser/FE

Length of output: 1000


🏁 Script executed:

#!/bin/bash
# 유사 입력 필드 구현 패턴 찾기
rg -n -A3 -B3 'readOnly.*input|readonly.*input' packages/design-system/src/components/ --type js --type jsx

Repository: CUK-Compasser/FE

Length of output: 87


읽기 전용 입력 필드의 키보드 접근성 확보

라인 43의 input에 onClick={onInputClick}만 연결되어 있어 마우스 사용자만 상호작용 가능합니다. inputReadOnly가 true일 때 이 필드가 "선택 트리거"로 동작한다면, 키보드 사용자가 Tab으로 포커스한 후 Enter 또는 Space로 상호작용할 수 있도록 onKeyDown 핸들러를 추가해주세요.

<input
  onKeyDown={(e) => {
    if ((e.key === 'Enter' || e.key === ' ') && inputReadOnly) {
      onInputClick?.(e as unknown as React.MouseEvent<HTMLInputElement>);
    }
  }}
  // ... 기존 props
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/design-system/src/components/Header/Header.tsx` at line 43, The
input currently only handles mouse interaction via onInputClick; add a keyboard
handler so read-only inputs acting as selection triggers are usable via
keyboard: add an onKeyDown on the same input that checks for Enter or Space when
inputReadOnly is true, preventDefault for Space, and invoke onInputClick
(casting the KeyboardEvent to the expected event type or adapting the handler
call) so keyboard users can activate the control; reference the inputReadOnly
prop and the onInputClick handler in Header.tsx and ensure the new onKeyDown
logic coexists with the existing onClick.

@skyblue1232 skyblue1232 merged commit 7df0816 into develop Apr 4, 2026
3 of 5 checks passed
@skyblue1232 skyblue1232 deleted the feat/#83/list-map-apis branch April 4, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api 서버 - 클라이언트 통신 chore 자잘한 수정 feat 기능 구현 및 생성

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 사용자 메인 리스트-맵 인터랙션 페이지 API 연동

1 participant