Skip to content

[Feat/#106] 사장님 가게 등록 관련 API 연동 #111

Merged
skyblue1232 merged 5 commits into
developfrom
feat/#106/store-interaction
May 4, 2026
Merged

[Feat/#106] 사장님 가게 등록 관련 API 연동 #111
skyblue1232 merged 5 commits into
developfrom
feat/#106/store-interaction

Conversation

@skyblue1232

@skyblue1232 skyblue1232 commented May 4, 2026

Copy link
Copy Markdown
Contributor

✅ 작업 내용

📝 Description

점주 스토어 등록/수정 페이지 API 연동 및 데이터 구조 정합성 개선

  • 내 가게 조회/수정 API 연동
  • 가게 위치 수정 API 연동
  • 랜덤박스 조회/등록/삭제 API 연동
  • 대표 이미지 조회/업로드/삭제 API 연동
  • 카카오 주소 검색 BottomSheet 연동
  • businessHours / bankType 서버 스펙 반영

🚀 설계 의도 및 개선점

프론트 상태 구조를 Swagger 응답/요청 스펙과 일치시키고, 등록/수정 페이지가 동일한 흐름으로 동작하도록 통일했습니다.

  • businessHours를 weekly(MON~SUN) 구조로 리팩토링
  • 은행명 UI와 bankType enum 서버 값 분리 처리
  • 주소 검색을 공통 BottomSheet로 분리해 재사용성 확보
  • 랜덤박스 수정 로직 제거 후 조회/추가/삭제 중심으로 단순화
  • register / store-info 페이지 상태 및 API 처리 구조 통일

📎 기타 참고사항

  • 기존 mock 기반 입력값 → 실제 API 응답 기반 초기값 반영
  • Kakao services script 필요

Fixes #106

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 주소 검색 기능 추가 - 하단 시트에서 주소를 검색하고 선택할 수 있습니다.
    • 랜덤박스 픽업 시간 설정 - 시작 및 종료 시간을 지정할 수 있습니다.
    • 매장 태그 관리 시스템 추가
  • Improvements

    • 매장 정보 편집 페이지 기능 개선
    • 영업시간 관리 구조 업데이트
    • 은행 정보 처리 방식 개선

@skyblue1232 skyblue1232 self-assigned this May 4, 2026
@skyblue1232 skyblue1232 added feat 기능 구현 및 생성 chore 자잘한 수정 api 서버 - 클라이언트 통신 style 스타일 관련 적용 labels May 4, 2026
@vercel

vercel Bot commented May 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
compasser-customer Ready Ready Preview, Comment May 4, 2026 1:01pm
compasser-owner Ready Ready Preview, Comment May 4, 2026 1:01pm

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@skyblue1232 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 8 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 19afc677-e40f-4d31-9fac-ba86c69b80e9

📥 Commits

Reviewing files that changed from the base of the PR and between 7e37399 and e6c2add.

📒 Files selected for processing (1)
  • apps/owner/src/app/signup/register/_components/modals/BusinessHoursModal.tsx
📝 Walkthrough

Walkthrough

이 PR은 주소 검색 기능을 새로 추가하고, 상점 정보 편집 폼의 태그/은행/영업시간 처리를 리팩토링하며, 랜덤박스에 픽업 시간 정보를 추가합니다.

Changes

주소 검색 기능

계층 / 파일 요약
타입 정의
apps/owner/src/app/signup/register/_types/address-search.ts, apps/owner/src/shared/types/kakao.d.ts
AddressSearchItem 인터페이스와 Window.kakao.maps 전역 타입 선언을 추가하여 Kakao 지도 API와 주소 검색 결과 형태를 정의합니다.
주소 검색 API
apps/owner/src/app/signup/register/_apis/searchAddressBykakao.ts
Kakao Geocoder를 사용하여 키워드 기반 주소 검색을 수행하고 결과를 AddressSearchItem 배열로 반환합니다.
주소 검색 UI 컴포넌트
apps/owner/src/app/signup/register/_components/AddressSearchBottomSheet.tsx
250ms 디바운싱된 주소 검색 입력, 결과 목록 렌더링, 선택 처리를 포함하는 바텀 시트를 구현합니다.
폼 통합
apps/owner/src/app/(tabs)/mypage/store-info/page.tsx, apps/owner/src/app/signup/register/page.tsx
AddressSearchBottomSheet를 페이지에 추가하고 handleSelectAddress 핸들러를 통해 선택된 주소를 입력 필드에 반영합니다.

상점 정보 폼 리팩토링

계층 / 파일 요약
은행 유틸 업데이트
apps/owner/src/shared/utils/bank.ts
bankNameOptions 배열을 {label, value} 쌍의 bankOptions로 대체하고 filterBanks, getBankLabel 함수를 추가합니다.
은행 필드 리팩토링
apps/owner/src/app/signup/register/_components/fields/AccountField.tsx
props를 bankName/onChangeBankName에서 bankType/onChangeBankType로 변경하고 은행 선택 로직을 새 유틸 함수로 연결합니다.
태그 및 영업시간 유틸
apps/owner/src/app/signup/register/_utils/business-hours.ts
ServerDayKey, BusinessDayValue 타입을 도입하고 BusinessHoursValue를 주간 구조로 재정의하며, parseBusinessHours를 새 스키마로 재작성합니다.
상점 정보 페이지 통합
apps/owner/src/app/(tabs)/mypage/store-info/page.tsx
태그 옵션 매핑(tagMap, reverseTagMap), 은행 필드를 bankType으로 변경, 영업시간 렌더링을 주간 구조로 업데이트합니다.
회원가입 페이지 통합
apps/owner/src/app/signup/register/page.tsx
회원가입 페이지 초기화와 제출 시에도 동일한 태그, 은행, 영업시간 처리를 적용하고 StoreUpdateReqDTO 페이로드를 업데이트합니다.
API DTO 업데이트
packages/api/src/models/store.ts
StoreUpdateReqDTO에서 bankName 필드를 bankType으로 변경하고 tag 필드를 추가합니다.

랜덤박스 픽업 시간 추가

계층 / 파일 요약
모달 타입 확장
apps/owner/src/app/signup/register/_components/modals/RandomBoxModal.tsx
RandomBoxFormValuepickupTimeInfo 필드(start/end)를 추가하고 시간 입력 UI를 렌더링합니다.
API DTO 업데이트
packages/api/src/models/random-box.ts
RandomBoxCreateReqDTO에 선택적 pickupTimeInfo 필드를 추가하고 RandomBoxRespDTO에 응답 필드를 추가합니다.
페이지 폼 제출
apps/owner/src/app/signup/register/page.tsx
랜덤박스 제출 시 pickupTimeInfo를 요청 본문에 포함시킵니다.

Sequence Diagram

sequenceDiagram
    participant User as 사용자
    participant UI as AddressSearchBottomSheet
    participant Debounce as 디바운스 타이머
    participant Kakao as Kakao 지도 API
    participant Component as 상점 정보 폼

    User->>UI: 주소 검색 바텀시트 열기
    User->>UI: 주소 입력 (예: "강남")
    UI->>Debounce: 입력값 트리거 (250ms 대기)
    Debounce->>UI: 디바운스 완료
    UI->>Kakao: searchAddressByKakao("강남", 10)
    Kakao->>UI: 주소 결과 배열 반환
    UI->>UI: 결과 목록 렌더링
    User->>UI: 결과 항목 선택
    UI->>Component: onSelectAddress(AddressSearchItem)
    Component->>Component: inputAddress 상태 업데이트
    UI->>UI: 바텀시트 닫기
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

refactor, feature, store-info, address-search

Poem

🐰 주소 검색 네 날개로 날아오르고,
은행 필드는 타입으로 안녕히 인사하며,
영업시간 주간 구조로 가지런해지니,
픽업 시간도 함께 춤을 춘다네—
상점 폼이 한 뼘 더 우아해졌어!

🚥 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 연동'으로 변경 사항의 주요 목적(API 연동)을 명확하게 반영하고 있으며, PR 설명의 목적과 구현 내용(가게 등록/수정 페이지 API 연동)과 일치합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#106/store-interaction

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
Review rate limit: 0/1 reviews remaining, refill in 51 minutes and 8 seconds.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (1)
packages/api/src/models/random-box.ts (1)

25-25: ⚡ Quick win

SaleStatus | string 타입 확장으로 인한 타입 안전성 손실

SaleStatusstring 리터럴 유니언이라면 SaleStatus | string은 단순한 string으로 축소되어 타입 안전성이 제거됩니다. 런타임에 예상치 못한 값이 들어오더라도 TypeScript가 경고하지 않게 됩니다.

♻️ 수정 제안
-  saleStatus: SaleStatus | string;
+  saleStatus: SaleStatus;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/models/random-box.ts` at line 25, The saleStatus property is
declared as "SaleStatus | string" which collapses the union to plain string and
loses type safety; change the declaration on the RandomBox model (the saleStatus
field) to use the strong SaleStatus type only, and if external input may contain
arbitrary strings add a runtime validation/parse step (e.g., implement and call
a parseSaleStatus or validateSaleStatus helper that maps/throws for invalid
values) wherever RandomBox instances are created or deserialized so only valid
SaleStatus values are assigned.
🤖 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/owner/src/app/`(tabs)/mypage/store-info/page.tsx:
- Around line 91-102: The effect watching myStore doesn't initialize bank
account fields so saving can overwrite existing values; update the useEffect
that runs when myStore changes (the function using setStoreName, setStoreEmail,
setInputAddress, setBusinessHours and setSelectedTag) to also
setBankType(myStore.bankType ?? ""), setDepositor(myStore.depositor ?? ""), and
setBankAccount(myStore.bankAccount ?? "") (or corresponding local state setters)
so the local state mirrors existing myStore values and untouched fields are not
saved as empty/undefined.

In `@apps/owner/src/app/signup/register/_components/AddressSearchBottomSheet.tsx`:
- Around line 33-66: The effect in AddressSearchBottomSheet can allow stale,
slow searchAddressByKakao responses to overwrite newer results; update the
effect to cancel/ignore in-flight requests by creating an AbortController or a
local "cancelled" flag for each invocation (tied to debounceRef/useEffect) and
pass/associate it with searchAddressByKakao (or check the flag after await) so
that on cleanup (and when open becomes false) you mark the request cancelled and
skip setResults/setIsSearching if cancelled; ensure you clear debounceRef
timeout as now and also invalidate the current request in the same cleanup so
late responses are ignored.

In `@apps/owner/src/app/signup/register/_components/fields/AccountField.tsx`:
- Line 31: bankKeyword is initialized with useState(getBankLabel(bankType)) but
not updated when the bankType prop changes; add an effect that watches bankType
and calls setBankKeyword(getBankLabel(bankType)) to keep bankKeyword in sync
(use useEffect(() => setBankKeyword(getBankLabel(bankType)), [bankType]));
update references to bankKeyword/setBankKeyword accordingly so the displayed
bank label reflects prop updates.

In `@apps/owner/src/app/signup/register/_components/modals/RandomBoxModal.tsx`:
- Around line 87-88: The current checks in RandomBoxModal around
form.pickupTimeInfo (the two lines checking if (!form.pickupTimeInfo.start)
return; and if (!form.pickupTimeInfo.end) return;) only validate presence and
miss validating ordering; update the validation where the form is
submitted/validated (the submit handler or validation block that contains these
two checks) to also verify that form.pickupTimeInfo.start <
form.pickupTimeInfo.end, and if not, abort submission and surface a clear error
(e.g., set a form error or call the modal/form error handler) so users cannot
submit when the pickup end time is before or equal to the start time. Ensure you
reference and update the same RandomBoxModal validation/submit logic so the new
ordering check runs alongside the existing presence checks.

In `@apps/owner/src/app/signup/register/_utils/business-hours.ts`:
- Around line 17-19: parseBusinessHours가 반환하는 중첩 구조({ weekly: { MON:..., ... }
})와 맞지 않아 toFormValue와 handleSubmit에서 데이터가 유실됩니다: in toFormValue (uses
normalized = parseBusinessHours(source)) 참조 시 normalized.weekly에서 ServerDayKey
(예: "MON"/"TUE")로 조회하도록 변경하고 소문자 day 키 대신 올바른 서버 키로 변환하거나 normalized.weekly 전체를
순회해 값을 가져오세요; in handleSubmit 초기 acc를 얕은 복사({ ...EMPTY_BUSINESS_HOURS })가 아니라
EMPTY_BUSINESS_HOURS.weekly 구조를 유지하도록 깊은/구조적 복사하여 acc.weekly[...] =
"09:00-18:00" 형태로 할당하고 최종 반환/전송도 acc (또는 acc.weekly)를 사용해 weekly 필드가 업데이트되도록
고치세요 (참조: parseBusinessHours, toFormValue, handleSubmit, EMPTY_BUSINESS_HOURS,
BusinessHoursValue, weekly, ServerDayKey).

In `@apps/owner/src/app/signup/register/page.tsx`:
- Around line 90-101: The useEffect that hydrates myStore is missing
initialization of bank fields, so stored draft bank info can be lost; update the
effect to also call the bank-related setters (e.g., setBankType, setDepositor,
setBankAccount) with values from myStore (like myStore.bankType,
myStore.depositor, myStore.bankAccount) using nullish coalescing/defaults or any
existing mapping helpers you use for enums, mirroring the pattern used for
setStoreName/setInputAddress and the edit page logic to ensure existing account
info is prefilled.

In `@apps/owner/src/shared/types/kakao.d.ts`:
- Line 87: 빈 인터페이스로 인해 `@typescript-eslint/no-empty-object-type` 룰 위반이 발생하므로,
KakaoLatLng, KakaoMarkerImage, KakaoSize, KakaoPoint 인터페이스에 실제 멤버를 추가하거나 eslint
억제 주석을 붙여야 합니다; 특히 KakaoLatLng는 getLat(): number와 getLng(): number 메서드를 포함하도록
보완하고, 나머지 인터페이스(KakaoMarkerImage, KakaoSize, KakaoPoint)는 라이브러리 사용 형태에 맞는 최소한의
속성(예: width/height, x/y, url 등)을 선언하거나 불가피하면 interface 선언 바로 위에 /*
eslint-disable `@typescript-eslint/no-empty-object-type` */ 주석을 추가해 lint 실패를
방지하십시오.

In `@packages/api/src/models/random-box.ts`:
- Line 10: Change the saleStatus property type from the overly-broad union to
the exact literal type by updating the saleStatus declaration to use SaleStatus
(e.g., replace any occurrence of "saleStatus: SaleStatus | string" with
"saleStatus: SaleStatus") so the model preserves the discriminated union
benefits; keep pickupTimeInfo as JsonValue since getPickupTimeText expects a
JSON string and calls JSON.parse() on it.

---

Nitpick comments:
In `@packages/api/src/models/random-box.ts`:
- Line 25: The saleStatus property is declared as "SaleStatus | string" which
collapses the union to plain string and loses type safety; change the
declaration on the RandomBox model (the saleStatus field) to use the strong
SaleStatus type only, and if external input may contain arbitrary strings add a
runtime validation/parse step (e.g., implement and call a parseSaleStatus or
validateSaleStatus helper that maps/throws for invalid values) wherever
RandomBox instances are created or deserialized so only valid SaleStatus values
are assigned.
🪄 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: ca47d231-de4a-4355-9be4-7462530978a7

📥 Commits

Reviewing files that changed from the base of the PR and between 78f2734 and 7e37399.

📒 Files selected for processing (12)
  • apps/owner/src/app/(tabs)/mypage/store-info/page.tsx
  • apps/owner/src/app/signup/register/_apis/searchAddressBykakao.ts
  • apps/owner/src/app/signup/register/_components/AddressSearchBottomSheet.tsx
  • apps/owner/src/app/signup/register/_components/fields/AccountField.tsx
  • apps/owner/src/app/signup/register/_components/modals/RandomBoxModal.tsx
  • apps/owner/src/app/signup/register/_types/address-search.ts
  • apps/owner/src/app/signup/register/_utils/business-hours.ts
  • apps/owner/src/app/signup/register/page.tsx
  • apps/owner/src/shared/types/kakao.d.ts
  • apps/owner/src/shared/utils/bank.ts
  • packages/api/src/models/random-box.ts
  • packages/api/src/models/store.ts

Comment on lines 91 to 102
useEffect(() => {
if (!myStore) return;

setStoreName(myStore.storeName ?? "");
setStoreEmail("");
setStoreEmail(myStore.storeEmail ?? "");
setInputAddress(myStore.inputAddress ?? "");
setBusinessHours(parseBusinessHours(myStore.businessHours));
setSelectedTag(mapStoreTagToLabel(myStore.tag));

if (myStore.tag) {
setSelectedTag(reverseTagMap[myStore.tag as StoreTag] ?? "");
}
}, [myStore]);

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 | ⚡ Quick win

기존 계좌 정보가 초기화되지 않아 저장 시 값이 지워집니다.

이 effect에서 bankType, depositor, bankAccount를 로컬 상태로 옮기지 않고 있어서 수정 화면이 빈 값으로 열립니다. 사용자가 해당 필드를 건드리지 않고 저장하면 기존 계좌 정보가 "" / undefined로 덮일 수 있습니다.

🔧 수정 예시
  useEffect(() => {
    if (!myStore) return;

    setStoreName(myStore.storeName ?? "");
    setStoreEmail(myStore.storeEmail ?? "");
    setInputAddress(myStore.inputAddress ?? "");
+    setBankType(myStore.bankType ?? "");
+    setDepositor(myStore.depositor ?? "");
+    setBankAccount(myStore.bankAccount ?? "");
    setBusinessHours(parseBusinessHours(myStore.businessHours));

    if (myStore.tag) {
      setSelectedTag(reverseTagMap[myStore.tag as StoreTag] ?? "");
    }
  }, [myStore]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/owner/src/app/`(tabs)/mypage/store-info/page.tsx around lines 91 - 102,
The effect watching myStore doesn't initialize bank account fields so saving can
overwrite existing values; update the useEffect that runs when myStore changes
(the function using setStoreName, setStoreEmail, setInputAddress,
setBusinessHours and setSelectedTag) to also setBankType(myStore.bankType ??
""), setDepositor(myStore.depositor ?? ""), and
setBankAccount(myStore.bankAccount ?? "") (or corresponding local state setters)
so the local state mirrors existing myStore values and untouched fields are not
saved as empty/undefined.

Comment on lines +33 to +66
useEffect(() => {
if (!open) return;

const trimmedKeyword = keyword.trim();

if (!trimmedKeyword) {
setResults([]);
setIsSearching(false);
return;
}

if (debounceRef.current) {
window.clearTimeout(debounceRef.current);
}

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);
}
}, 250);

return () => {
if (debounceRef.current) {
window.clearTimeout(debounceRef.current);
}
};
}, [keyword, open]);

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:

# First, find and read the file
fd -type f -name "AddressSearchBottomSheet.tsx"

Repository: CUK-Compasser/FE

Length of output: 231


🏁 Script executed:

# Fix the command - use -type without dash for fd
fd "AddressSearchBottomSheet.tsx"

Repository: CUK-Compasser/FE

Length of output: 211


🏁 Script executed:

# Read the file to verify the code
cat -n "apps/owner/src/app/signup/register/_components/AddressSearchBottomSheet.tsx"

Repository: CUK-Compasser/FE

Length of output: 4297


이전 검색 응답이 최신 결과를 덮어쓸 수 있습니다.

지금은 타이머만 정리하고 이미 시작된 searchAddressByKakao() 호출은 무시하지 않아서, 느린 이전 응답이 최신 키워드 결과를 덮어쓸 수 있습니다. BottomSheet를 닫은 뒤 늦게 도착한 응답이 빈 입력창 아래에 이전 결과를 다시 채우는 문제도 생깁니다. cleanup에서 요청을 무효화한 뒤, 응답 반영 전에 그 플래그를 확인해 주세요.

🔧 수정 예시
  useEffect(() => {
    if (!open) return;

    const trimmedKeyword = keyword.trim();

    if (!trimmedKeyword) {
      setResults([]);
      setIsSearching(false);
      return;
    }

+    let cancelled = false;
+
     if (debounceRef.current) {
       window.clearTimeout(debounceRef.current);
     }

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

     return () => {
+      cancelled = true;
       if (debounceRef.current) {
         window.clearTimeout(debounceRef.current);
       }
     };
   }, [keyword, open]);
📝 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
useEffect(() => {
if (!open) return;
const trimmedKeyword = keyword.trim();
if (!trimmedKeyword) {
setResults([]);
setIsSearching(false);
return;
}
if (debounceRef.current) {
window.clearTimeout(debounceRef.current);
}
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);
}
}, 250);
return () => {
if (debounceRef.current) {
window.clearTimeout(debounceRef.current);
}
};
}, [keyword, open]);
useEffect(() => {
if (!open) return;
const trimmedKeyword = keyword.trim();
if (!trimmedKeyword) {
setResults([]);
setIsSearching(false);
return;
}
let cancelled = false;
if (debounceRef.current) {
window.clearTimeout(debounceRef.current);
}
debounceRef.current = window.setTimeout(async () => {
try {
setIsSearching(true);
const searched = await searchAddressByKakao(trimmedKeyword, 10);
if (cancelled) return;
setResults(searched);
} catch (error) {
if (cancelled) return;
console.error(error);
setResults([]);
} finally {
if (cancelled) return;
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/owner/src/app/signup/register/_components/AddressSearchBottomSheet.tsx`
around lines 33 - 66, The effect in AddressSearchBottomSheet can allow stale,
slow searchAddressByKakao responses to overwrite newer results; update the
effect to cancel/ignore in-flight requests by creating an AbortController or a
local "cancelled" flag for each invocation (tied to debounceRef/useEffect) and
pass/associate it with searchAddressByKakao (or check the flag after await) so
that on cleanup (and when open becomes false) you mark the request cancelled and
skip setResults/setIsSearching if cancelled; ensure you clear debounceRef
timeout as now and also invalidate the current request in the same cleanup so
late responses are ignored.

}: AccountFieldProps) {
const [step, setStep] = useState<AccountStep>("bank");
const [bankKeyword, setBankKeyword] = useState(bankName);
const [bankKeyword, setBankKeyword] = useState(getBankLabel(bankType));

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 | ⚡ Quick win

bankKeyword 상태가 bankType prop 변경 시 동기화되지 않음

useState(getBankLabel(bankType))는 첫 렌더링 시에만 초기값을 사용합니다. API 응답 이후 부모가 bankType을 갱신해도 bankKeyword는 빈 문자열로 남아, 은행명 입력 필드가 비어 있는 채로 표시됩니다.

🐛 수정 제안 — useEffect로 동기화
  const [bankKeyword, setBankKeyword] = useState(getBankLabel(bankType));
+
+  useEffect(() => {
+    if (bankType) {
+      setBankKeyword(getBankLabel(bankType));
+    }
+  }, [bankType]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/owner/src/app/signup/register/_components/fields/AccountField.tsx` at
line 31, bankKeyword is initialized with useState(getBankLabel(bankType)) but
not updated when the bankType prop changes; add an effect that watches bankType
and calls setBankKeyword(getBankLabel(bankType)) to keep bankKeyword in sync
(use useEffect(() => setBankKeyword(getBankLabel(bankType)), [bankType]));
update references to bankKeyword/setBankKeyword accordingly so the displayed
bank label reflects prop updates.

Comment on lines +87 to +88
if (!form.pickupTimeInfo.start) return;
if (!form.pickupTimeInfo.end) 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 | ⚡ Quick win

픽업 종료 시간이 시작 시간보다 이른 경우를 검증하지 않음

현재 비어있는지만 확인할 뿐, start < end 검증이 없어 종료 시간이 시작 시간보다 앞선 값도 제출될 수 있습니다.

🛡️ 수정 제안
    if (!form.pickupTimeInfo.start) return;
    if (!form.pickupTimeInfo.end) return;
+   if (form.pickupTimeInfo.start >= form.pickupTimeInfo.end) 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
if (!form.pickupTimeInfo.start) return;
if (!form.pickupTimeInfo.end) return;
if (!form.pickupTimeInfo.start) return;
if (!form.pickupTimeInfo.end) return;
if (form.pickupTimeInfo.start >= form.pickupTimeInfo.end) return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/owner/src/app/signup/register/_components/modals/RandomBoxModal.tsx`
around lines 87 - 88, The current checks in RandomBoxModal around
form.pickupTimeInfo (the two lines checking if (!form.pickupTimeInfo.start)
return; and if (!form.pickupTimeInfo.end) return;) only validate presence and
miss validating ordering; update the validation where the form is
submitted/validated (the submit handler or validation block that contains these
two checks) to also verify that form.pickupTimeInfo.start <
form.pickupTimeInfo.end, and if not, abort submission and surface a clear error
(e.g., set a form error or call the modal/form error handler) so users cannot
submit when the pickup end time is before or equal to the start time. Ensure you
reference and update the same RandomBoxModal validation/submit logic so the new
ordering check runs alongside the existing presence checks.

Comment thread apps/owner/src/app/signup/register/_utils/business-hours.ts
Comment on lines 90 to 101
useEffect(() => {
if (!myStore) return;
if (!myStore) return;

setStoreName(myStore.storeName ?? "");
setStoreEmail("");
setStoreEmail(myStore.storeEmail ?? "");
setInputAddress(myStore.inputAddress ?? "");
setBusinessHours(parseBusinessHours(myStore.businessHours));

if (myStore.tag) {
setSelectedTag(reverseTagMap[myStore.tag as StoreTag] ?? "");
}
}, [myStore]);

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 | ⚡ Quick win

등록 페이지도 기존 계좌 정보를 hydrate하지 않고 있습니다.

여기도 myStore에서 bankType, depositor, bankAccount를 세팅하지 않아서, 저장된 초안이 있어도 입력창은 비어 보이고 제출 시 기존 값이 비워질 수 있습니다. 위 수정 페이지와 동일하게 초기 상태를 채워 주세요.

🔧 수정 예시
  useEffect(() => {
    if (!myStore) return;

    setStoreName(myStore.storeName ?? "");
    setStoreEmail(myStore.storeEmail ?? "");
    setInputAddress(myStore.inputAddress ?? "");
+    setBankType(myStore.bankType ?? "");
+    setDepositor(myStore.depositor ?? "");
+    setBankAccount(myStore.bankAccount ?? "");
    setBusinessHours(parseBusinessHours(myStore.businessHours));

    if (myStore.tag) {
      setSelectedTag(reverseTagMap[myStore.tag as StoreTag] ?? "");
    }
  }, [myStore]);
📝 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
useEffect(() => {
if (!myStore) return;
if (!myStore) return;
setStoreName(myStore.storeName ?? "");
setStoreEmail("");
setStoreEmail(myStore.storeEmail ?? "");
setInputAddress(myStore.inputAddress ?? "");
setBusinessHours(parseBusinessHours(myStore.businessHours));
if (myStore.tag) {
setSelectedTag(reverseTagMap[myStore.tag as StoreTag] ?? "");
}
}, [myStore]);
useEffect(() => {
if (!myStore) return;
setStoreName(myStore.storeName ?? "");
setStoreEmail(myStore.storeEmail ?? "");
setInputAddress(myStore.inputAddress ?? "");
setBankType(myStore.bankType ?? "");
setDepositor(myStore.depositor ?? "");
setBankAccount(myStore.bankAccount ?? "");
setBusinessHours(parseBusinessHours(myStore.businessHours));
if (myStore.tag) {
setSelectedTag(reverseTagMap[myStore.tag as StoreTag] ?? "");
}
}, [myStore]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/owner/src/app/signup/register/page.tsx` around lines 90 - 101, The
useEffect that hydrates myStore is missing initialization of bank fields, so
stored draft bank info can be lost; update the effect to also call the
bank-related setters (e.g., setBankType, setDepositor, setBankAccount) with
values from myStore (like myStore.bankType, myStore.depositor,
myStore.bankAccount) using nullish coalescing/defaults or any existing mapping
helpers you use for enums, mirroring the pattern used for
setStoreName/setInputAddress and the edit page logic to ensure existing account
info is prefilled.

};
}

interface KakaoLatLng {}

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 | ⚡ Quick win

빈 인터페이스 ESLint 오류 — CI 빌드 실패 가능성

KakaoLatLng, KakaoMarkerImage, KakaoSize, KakaoPoint 4개 인터페이스가 @typescript-eslint/no-empty-object-type 오류를 발생시킵니다. ESLint 오류 수준이므로 CI lint 단계에서 실패할 수 있습니다.

의미 있는 멤버를 추가하거나 eslint 억제 주석을 사용하는 방법으로 수정할 수 있습니다. KakaoLatLng는 실제로 getLat()/getLng() 메서드를 가지므로 타입을 보완하는 것이 가장 좋습니다.

🔧 수정 제안
-  interface KakaoLatLng {}
+  interface KakaoLatLng {
+    getLat: () => number;
+    getLng: () => number;
+  }

-  interface KakaoMarkerImage {}
+  // eslint-disable-next-line `@typescript-eslint/no-empty-object-type`
+  interface KakaoMarkerImage {}

-  interface KakaoSize {}
+  // eslint-disable-next-line `@typescript-eslint/no-empty-object-type`
+  interface KakaoSize {}

-  interface KakaoPoint {}
+  // eslint-disable-next-line `@typescript-eslint/no-empty-object-type`
+  interface KakaoPoint {}

Also applies to: 99-99, 101-101, 103-103

🧰 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)

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

In `@apps/owner/src/shared/types/kakao.d.ts` at line 87, 빈 인터페이스로 인해
`@typescript-eslint/no-empty-object-type` 룰 위반이 발생하므로, KakaoLatLng,
KakaoMarkerImage, KakaoSize, KakaoPoint 인터페이스에 실제 멤버를 추가하거나 eslint 억제 주석을 붙여야
합니다; 특히 KakaoLatLng는 getLat(): number와 getLng(): number 메서드를 포함하도록 보완하고, 나머지
인터페이스(KakaoMarkerImage, KakaoSize, KakaoPoint)는 라이브러리 사용 형태에 맞는 최소한의 속성(예:
width/height, x/y, url 등)을 선언하거나 불가피하면 interface 선언 바로 위에 /* eslint-disable
`@typescript-eslint/no-empty-object-type` */ 주석을 추가해 lint 실패를 방지하십시오.

stock?: number;
price?: number;
buyLimit?: number;
pickupTimeInfo?: JsonValue;

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
# pickupTimeInfo 응답 타입을 string으로 사용하는 모든 소비 코드 확인
rg -n "pickupTimeInfo" --type ts -A 3 -B 1

Repository: CUK-Compasser/FE

Length of output: 6370


🏁 Script executed:

# Check JsonValue type definition
rg -n "type JsonValue|interface JsonValue" --type ts -A 5

Repository: CUK-Compasser/FE

Length of output: 393


🏁 Script executed:

# Find getPickupTimeText function
rg -n "getPickupTimeText" --type ts -B 2 -A 8

Repository: CUK-Compasser/FE

Length of output: 2547


🏁 Script executed:

# Check SaleStatus definition
rg -n "enum SaleStatus|type SaleStatus" --type ts -A 10

Repository: CUK-Compasser/FE

Length of output: 635


🏁 Script executed:

# Check if there's any JSON serialization/deserialization in API handlers
rg -n "pickupTimeInfo" --type ts packages/api -B 3 -A 3

Repository: CUK-Compasser/FE

Length of output: 1193


saleStatus: SaleStatus | string 타입을 SaleStatus로 정정 필요

SaleStatus는 이미 문자열 리터럴 유니온 타입("SOLD_OUT" | "READY" | "CANCELED")이므로, SaleStatus | string은 중복되어 평문 string으로 축약됩니다. 이로 인해 타입 안전성과 판별식 유니온의 이점이 사라집니다. 25번 줄을 saleStatus: SaleStatus로 수정하세요.

pickupTimeInfo의 경우, 요청 시 객체(JsonValue)를 받고 응답 시 JSON 직렬화 문자열로 반환하는 설계입니다. 소비 코드(getPickupTimeText)에서 문자열에 대해 JSON.parse()를 명시적으로 호출하므로 의도적이며 정상 작동합니다.

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

In `@packages/api/src/models/random-box.ts` at line 10, Change the saleStatus
property type from the overly-broad union to the exact literal type by updating
the saleStatus declaration to use SaleStatus (e.g., replace any occurrence of
"saleStatus: SaleStatus | string" with "saleStatus: SaleStatus") so the model
preserves the discriminated union benefits; keep pickupTimeInfo as JsonValue
since getPickupTimeText expects a JSON string and calls JSON.parse() on it.

@skyblue1232 skyblue1232 merged commit 45a753c into develop May 4, 2026
5 checks passed
@skyblue1232 skyblue1232 deleted the feat/#106/store-interaction branch May 12, 2026 02:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api 서버 - 클라이언트 통신 chore 자잘한 수정 feat 기능 구현 및 생성 style 스타일 관련 적용

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 사용자/사장님 랜덤박스 및 가게 관련 API 연동

1 participant