Skip to content

feat: 기사 상세 페이지 지정 견적 요청 기능 구현 - #30

Merged
yooseohyeon merged 19 commits into
devfrom
feat/designated-estimate-request-api
Jul 30, 2026
Merged

feat: 기사 상세 페이지 지정 견적 요청 기능 구현#30
yooseohyeon merged 19 commits into
devfrom
feat/designated-estimate-request-api

Conversation

@yooseohyeon

@yooseohyeon yooseohyeon commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

📋 작업 내용

기사 상세 페이지에서 지정 견적 요청 기능을 구현했습니다.

  • API 연동 및 예외 처리 (비회원 / 활성 견적 없음 / 이미 지정 / 3명 초과 / 확정·만료 등)
  • 각 예외 상황에 맞는 모달 또는 Toast 안내를 추가하여 사용자 경험을 개선했습니다.
  • 서버 검증과 별개로 클라이언트 게이트를 두어 이중 방어 구조로 설계했습니다.

🔥 변경 사항

API / 훅

지정 견적 요청 API 추가

  • designateMover API 클라이언트 추가
  • useDesignateMover hook 추가: 지정 요청 mutation + 캐시 갱신

기존 active 견적 API 변경

  • getActiveEstimateRequest axiosInstancefetchInstance로 전환
    • 래퍼 파싱 로직 제거 (fetchInstance가 내부에서 처리)
    • 반환 타입을 unknown | nullMyEstimateRequestItem | null로 명확하게 변경
  • useActiveEstimateRequest hook으로 분리해 공용화
    • 변경 전: EstimateRequestForm 내부에서 useQuery로 직접 조회해, 해당 컴포넌트에서만 사용할 수 있었음
    • 변경 후: 독립 훅으로 분리해 EstimateRequestFormMoverDetailView 양쪽에서 재사용
  • 견적 요청 생성 성공 시 ACTIVE 캐시에 boolean(true)을 넣지 않도록 수정 (리뷰 반영)
    • 응답이 있으면 MyEstimateRequestItem을 그대로 저장하고, 없으면 invalidate로 재조회

CTA 상태 관리

  • getDesignateCtaState 유틸 추가: GET /active 응답 데이터에 들어있는 요청이 지정 가능한지 클라이언트에서 먼저 검사하고, 서버에서 한 번 더 검증하는 이중 방어 구조
상태 조건 처리
needEstimateRequest 활성 견적 요청 없음 일반 견적 요청 안내 모달
notEditable 상태가 PENDING/OPEN이 아닌 경우 버튼 비활성 + 라벨 "지정 견적 요청 불가"
expired 해당 요청이 만료된 경우(expiresAt만 지남) 버튼 비활성 + 라벨 "지정 견적 요청 불가"
alreadyDesignated 이미 해당 기사 지정됨 버튼 비활성 + 라벨 "지정 견적 요청 완료"
limitExceeded 지정 기사 3명 초과 버튼 비활성 + 라벨 "지정 한도 초과"
available 모든 조건 통과 API 호출
  • notEditable, expired/active는 진행 중인 요청만 전달해주기 때문에 정상 흐름에서는 발생할 경우가 적지만, 캐시에 예전 active가 남아 있거나, 응답이 오는 사이에 만료/확정된 경우 등 예외적인 상황을 위한 방어 로직입니다.

  • CTA의 목적을 고려해 상태와 만료 검사를 alreadyDesignated보다 먼저 수행하도록 수정 (리뷰 반영)

    • 사용자에게 해당 기사에게 지정 견적 요청이 가능한지 알려줘야 하므로, 지정 견적 요청 유무 그자체보다는 불가능한 사유를 먼저 검사하고 명확하게 알려주는 것이 더 자연스럽다고 판단함

기사 상세 페이지

  • MoverDetailView: 로그인 → 활성 견적 확인 → 지정 요청 전체 플로우 연결
    • 활성 견적 조회 실패(isError) 시 기존 CTA 처리를 막고, Toast로 재시도 안내 표시 (리뷰 반영)
  • MoverDetailActions:requestDisabled, requestButtonLabel prop 추가
  • EstimateRequestRequiredModal 추가: 일반 견적 요청이 없을 때 안내
  • DesignateSuccessModal 추가: 지정 요청 완료 시 안내 + "지정 견적서 보러가기" 이동
  • LoginRequiredModaldescription prop 추가: 호출 맥락에 따라 문구 변경 가능 (찜 / 지정 견적 등)
  • 활성 견적 조회 실패 시 Toast를 표시하고, 버튼을 다시 클릭하면 refetch하도록 수정 (리뷰 반영)

공통 컴포넌트 확장

  • Modal.Title: variant, className prop 추가
  • Modal.Desc: variant prop 추가
  • Modal.Close: 반응형 사이즈 적용 (24px/md:36px)
  • Button: solid/outline disabled 스타일 추가
  • 모든 prop에 기본값을 두어 기존 사용처에 영향이 없도록 했습니다.

기타

  • /estimates/requests: 일반 고객이 자신이 보낸 견적을 확인할 수 있는 페이지 추가, 임시 플레이스홀더 페이지

✅ 체크리스트

  • 로컬에서 정상 동작을 확인했습니다.
  • 기존 기능에 영향을 주지 않는지 확인했습니다.
  • 불필요한 console.log를 제거했습니다.
  • lint를 통과했습니다.
  • README 또는 문서를 수정했습니다. (필요 시)
  • API 명세와 일치하는지 확인했습니다.

📷 스크린샷 (선택)

기본

localhost_3000_movers_9b2c2c04-5ce9-45d0-bc0f-f5e6e5a99021 (5)

지정 견적 요청 처리 중

localhost_3000_movers_9b2c2c04-5ce9-45d0-bc0f-f5e6e5a99021 (6)

지정 견적 요청 완료

localhost_3000_movers_9b2c2c04-5ce9-45d0-bc0f-f5e6e5a99021 (7)

이미 해당 기사를 지정한 경우 (버튼 비활성화)

localhost_3000_movers_9b2c2c04-5ce9-45d0-bc0f-f5e6e5a99021 (8)

비회원 (모달)

localhost_3000_movers_9b2c2c04-5ce9-45d0-bc0f-f5e6e5a99021 (1)

활성 견적 요청이 없음 (모달)

localhost_3000_movers_9b2c2c04-5ce9-45d0-bc0f-f5e6e5a99021 (2)

기사 3명 초과함 (버튼 비활성화)

localhost_3000_movers_c16727ef-4ba7-4077-881a-9df6026aa258

💬 To Reviewer

⚠️ 이미 확정된 요청이 있는 경우 플로우

  • 이미 확정된 요청이 있는 경우에 이를 알리고 지정 견적 요청을 막는 로직은 현재 불가능합니다.
    • 요청이 확정되면 활성 견적 요청은 null이 되기 때문에, "한 번 견적 요청을 하지 않음"과 똑같이 간주되기 때문입니다. 이 경우 일반 견적 요청이 먼저 필요하다는 모달이 뜹니다.
    • 현재는 전체 사용 플로우를 검증할 수 없는 상황이라 변경이 필요한지 확신이 서지 않습니다. 그래서 추후 변경이 필요하면, 지정 요청 목록을 조회하는 API를 추가로 연동해 확정된 요청이 있는지 거르거나 /active API 응답을 수정하고자 합니다.

getDesignateCtaState 추가

  • 서버 API 호출 전에 클라이언트에서 활성 견적 데이터(status, expiresAt, designatedMovers)를 읽어 먼저 차단합니다.
  • 이 게이트를 통과하더라도 서버가 최종 검증하므로, 불필요한 API 호출을 줄이면서도 안전성을 확보하고자 했는데, 이렇게 구현해도 되는지 확인 부탁드립니다!

Modal 관련 (별도 리팩토링 예정)

  • 프로젝트에 Modal.tsxModal/Modal.tsx가 혼재합니다. 아직 ui를 구현 중인 단계이며, 모달 디자인이 확정되지 않았음을 고려해 모달 통합은 별도 리팩토링으로 진행할 예정입니다.
  • 모달이 열릴 때 스크롤바가 사라지면서 fixed/absolute 요소와 좌우 어긋남이 발생하는 이슈가 있습니다. ModalMain 수정이 필요한 영역이라 이번 PR 범위에서 제외했습니다.
  • LoginRequiredModalEstimateRequestRequiredModal에 동일한 패널 스타일 상수(PANEL_CLASSNAME)가 각각 로컬로 들어있습니다. 사용처가 2곳뿐이고 도메인(auth/estimate)이 달라 중복을 허용했습니다. (추상화 시 이점보다 비용이 더 들 것이라 예상) 모달 통합 시 함께 정리할 예정입니다.

기타

  • /estimates/requests는 고객이 보낸 견적 요청을 확인하는 페이지로, 아직 구현 전인 임시 플레이스홀더입니다.

Summary by CodeRabbit

Summary

  • 새로운 기능

    • 지정 견적 요청 CTA가 로그인/활성 견적 상태(만료·한도 초과·이미 지정)에 따라 메시지와 버튼 동작(가능/불가)을 동적으로 제공합니다.
    • “지정 견적 요청” 안내 모달과 “지정 완료” 성공 모달을 추가했습니다.
  • 개선 사항

    • 모달 제목/설명/닫기 버튼의 표현과 비활성(disabled) 시각 효과를 개선했습니다.
    • 버튼 disabled 스타일을 상황별로 더 일관되게 정리했습니다.
    • 리뷰 작성일 표기를 YYYY-MM-DD로 통일했습니다.

@yooseohyeon
yooseohyeon requested a review from juengseulki July 29, 2026 08:37
@yooseohyeon yooseohyeon self-assigned this Jul 29, 2026
@yooseohyeon yooseohyeon changed the title feat: 지정 견적 요청 기능 구현 feat: 기사 상세 페이지 지정 견적 요청 기능 구현 Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

활성 견적 조회와 기사 지정 API·훅을 추가하고, 기사 상세 CTA에서 로그인·일반 견적 요청·기사 지정 흐름을 상태에 따라 처리하도록 연결했습니다. 관련 모달, 버튼 스타일, 견적 폼 조회, 리뷰 날짜 표시와 견적 요청 목록 페이지도 변경했습니다.

Changes

지정 견적 요청

Layer / File(s) Summary
견적 API와 조회·mutation 훅
src/lib/api/estimateRequest.ts, src/lib/constants/apiRoutes.ts, src/hooks/useActiveEstimateRequest.ts, src/hooks/useDesignateMover.ts, src/lib/api/getApiError.ts
견적 API 응답을 직접 반환하도록 변경하고 기사 지정 API, 라우트, 활성 견적 조회 훅과 mutation 훅을 추가했습니다.
CTA 상태와 지정 요청 분기
src/lib/utils/getDesignateCtaState.ts, src/components/mover/detail/MoverDetailView.tsx, src/components/mover/detail/MoverDetailActions.tsx, src/components/estimate/EstimateRequestRequiredModal.tsx, src/lib/constants/appRoutes.ts
활성 요청과 세션 상태에 따라 버튼 상태를 계산하고 로그인·일반 견적 요청 모달 또는 기사 지정 요청으로 분기합니다.
로그인 및 공통 모달 표현
src/components/auth/*, src/components/common/Button/Button.tsx, src/components/common/Modal/*, src/components/estimate/DesignateSuccessModal.tsx
로그인 모달 설명 전달, 제목·설명 variant, 반응형 버튼, 닫기 버튼과 disabled 버튼 스타일을 변경하고 지정 완료 모달을 추가했습니다.
견적 소비 화면과 날짜 표시
src/components/estimate/EstimateRequestForm.tsx, src/lib/utils/estimateFormat.ts, src/components/mover/detail/MoverDetailReviews.tsx, src/app/estimates/requests/page.tsx
활성 견적 조회를 전용 훅으로 전환하고 캐시 저장 형태, 리뷰 날짜 포맷과 견적 요청 목록 페이지를 변경했습니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant MoverDetailView
  participant CTAState
  participant Modal
  participant DesignateMutation
  participant EstimateRequestAPI
  MoverDetailView->>CTAState: 활성 견적과 moverId로 CTA 상태 계산
  MoverDetailView->>Modal: 로그인 또는 일반 견적 요청 모달 표시
  MoverDetailView->>DesignateMutation: 제출 가능한 지정 요청 실행
  DesignateMutation->>EstimateRequestAPI: POST /estimate-requests/:id/designate
  EstimateRequestAPI-->>DesignateMutation: 지정된 견적 요청 반환
  DesignateMutation-->>MoverDetailView: 캐시 갱신 및 성공 처리
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 기사 상세 페이지의 지정 견적 요청 기능 추가라는 핵심 변경을 정확히 요약합니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/designated-estimate-request-api

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

@yooseohyeon
yooseohyeon requested a review from youngmis July 29, 2026 08:40

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/common/Modal/ModalClose.tsx (1)

16-25: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

모바일 닫기 버튼의 터치 영역을 확대해 주세요.

현재 버튼과 아이콘이 모두 24px이라 모바일에서 누르기 어렵습니다. 아이콘은 유지하되 버튼 hit target은 최소 44px 수준으로 확보해 주세요.

As per path instructions, 모바일 터치 영역을 확인해야 합니다.

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

In `@src/components/common/Modal/ModalClose.tsx` around lines 16 - 25, Update the
button sizing classes in ModalClose so its mobile hit target is at least 44px
while preserving the CloseIcon size-24 rendering; retain the existing md:size-36
behavior and surrounding interaction styles.

Source: Path instructions

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

Inline comments:
In `@src/components/auth/LoginRequiredModal.tsx`:
- Around line 53-55: Update the Modal.Desc element in LoginRequiredModal to
remove the whitespace-nowrap class, allowing long descriptions to wrap naturally
within the mobile panel while preserving the existing styling and responsive
layout handling.

In `@src/components/estimate/EstimateRequestRequiredModal.tsx`:
- Around line 47-49: Remove the whitespace-nowrap class from the Modal.Desc
element in EstimateRequestRequiredModal so the Korean description can wrap
naturally within the modal’s available content width. Preserve the existing
variant, sizing, and other class styling.

In `@src/components/mover/detail/MoverDetailView.tsx`:
- Around line 51-56: Update the designateMutation onSuccess flow in
MoverDetailView to show the “지정 견적 요청 완료” completion modal and connect its “지정
견적 보러가기” action to navigate to the designated quote, while preserving the
existing success toast behavior and onError handling.
- Around line 47-49: Update the useActiveEstimateRequest flow in MoverDetailView
to also consume isError, distinguishing query failures from a successful
response with no active request. When isError is true, show the existing retry
guidance and prevent CTA/modal calculation from treating activeRequest as null;
retain the current no-request behavior only for successful queries without data.

---

Outside diff comments:
In `@src/components/common/Modal/ModalClose.tsx`:
- Around line 16-25: Update the button sizing classes in ModalClose so its
mobile hit target is at least 44px while preserving the CloseIcon size-24
rendering; retain the existing md:size-36 behavior and surrounding interaction
styles.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d4b888b3-245c-4166-b82c-3b128b7de2f2

📥 Commits

Reviewing files that changed from the base of the PR and between 239654d and 344932c.

📒 Files selected for processing (19)
  • src/components/auth/LoginRequiredModal.tsx
  • src/components/auth/LoginRequiredModalProvider.tsx
  • src/components/common/Button/Button.tsx
  • src/components/common/Modal/ModalClose.tsx
  • src/components/common/Modal/ModalDescription.tsx
  • src/components/common/Modal/ModalTitle.tsx
  • src/components/estimate/EstimateRequestForm.tsx
  • src/components/estimate/EstimateRequestRequiredModal.tsx
  • src/components/mover/detail/MoverDetailActions.tsx
  • src/components/mover/detail/MoverDetailReviews.tsx
  • src/components/mover/detail/MoverDetailView.tsx
  • src/hooks/useActiveEstimateRequest.ts
  • src/hooks/useDesignateMover.ts
  • src/lib/api/estimateRequest.ts
  • src/lib/api/getApiError.ts
  • src/lib/constants/apiRoutes.ts
  • src/lib/constants/appRoutes.ts
  • src/lib/utils/estimateFormat.ts
  • src/lib/utils/getDesignateCtaState.ts

Comment thread src/components/auth/LoginRequiredModal.tsx Outdated
Comment thread src/components/estimate/EstimateRequestRequiredModal.tsx Outdated
Comment thread src/components/mover/detail/MoverDetailView.tsx Outdated
Comment thread src/components/mover/detail/MoverDetailView.tsx
@yooseohyeon
yooseohyeon force-pushed the feat/designated-estimate-request-api branch from 344932c to afb35a8 Compare July 29, 2026 08:56

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

🧹 Nitpick comments (1)
src/components/estimate/DesignateSuccessModal.tsx (1)

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

클래스 조합에 cn()을 사용해 주세요.

저장소 규칙상 Tailwind 클래스 조합은 src/lib/utils/cn.tscn()을 사용해야 합니다. 정적 클래스라도 배열 join(" ") 대신 기존 유틸을 사용하면 스타일 조합 방식을 일관되게 유지할 수 있습니다.

수정 예시
+import { cn } from "`@/lib/utils/cn`";
+
-const PANEL_CLASSNAME = [
-  "items-stretch text-left",
-  "rounded-24 md:rounded-32",
-  "w-full max-w-[292px] gap-30 px-16 py-24",
-  "md:max-w-[608px] md:gap-40 md:px-24 md:pt-32 md:pb-40",
-].join(" ");
+const PANEL_CLASSNAME = cn(
+  "items-stretch text-left",
+  "rounded-24 md:rounded-32",
+  "w-full max-w-[292px] gap-30 px-16 py-24",
+  "md:max-w-[608px] md:gap-40 md:px-24 md:pt-32 md:pb-40",
+);

As per path instructions: 클래스 조합에는 cn()을 사용해야 합니다.

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

In `@src/components/estimate/DesignateSuccessModal.tsx` around lines 8 - 13,
Update the PANEL_CLASSNAME definition in DesignateSuccessModal to use the
repository’s cn() utility from src/lib/utils/cn.ts instead of assembling the
Tailwind classes with an array and join(" "). Preserve the existing class values
and ordering.

Source: Path instructions

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

Inline comments:
In `@src/app/estimates/requests/page.tsx`:
- Around line 1-15: 페이지 모듈에 Next.js metadata를 추가해 기본 탭 제목과 설명을 설정하세요.
EstimateRequestsPage 주변에 metadata를 export하고, 다른 estimates 페이지와 동일한 형식으로 이 페이지에
맞는 title과 description을 지정하세요.

In `@src/components/estimate/DesignateSuccessModal.tsx`:
- Around line 41-43: Update the Modal.Desc element in DesignateSuccessModal to
use whitespace-normal instead of whitespace-nowrap, allowing the completion
message to wrap within the mobile modal width without overflowing.

---

Nitpick comments:
In `@src/components/estimate/DesignateSuccessModal.tsx`:
- Around line 8-13: Update the PANEL_CLASSNAME definition in
DesignateSuccessModal to use the repository’s cn() utility from
src/lib/utils/cn.ts instead of assembling the Tailwind classes with an array and
join(" "). Preserve the existing class values and ordering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 43140dcd-ac05-43e1-9e94-860bb83eec71

📥 Commits

Reviewing files that changed from the base of the PR and between 344932c and afb35a8.

📒 Files selected for processing (21)
  • src/app/estimates/requests/page.tsx
  • src/components/auth/LoginRequiredModal.tsx
  • src/components/auth/LoginRequiredModalProvider.tsx
  • src/components/common/Button/Button.tsx
  • src/components/common/Modal/ModalClose.tsx
  • src/components/common/Modal/ModalDescription.tsx
  • src/components/common/Modal/ModalTitle.tsx
  • src/components/estimate/DesignateSuccessModal.tsx
  • src/components/estimate/EstimateRequestForm.tsx
  • src/components/estimate/EstimateRequestRequiredModal.tsx
  • src/components/mover/detail/MoverDetailActions.tsx
  • src/components/mover/detail/MoverDetailReviews.tsx
  • src/components/mover/detail/MoverDetailView.tsx
  • src/hooks/useActiveEstimateRequest.ts
  • src/hooks/useDesignateMover.ts
  • src/lib/api/estimateRequest.ts
  • src/lib/api/getApiError.ts
  • src/lib/constants/apiRoutes.ts
  • src/lib/constants/appRoutes.ts
  • src/lib/utils/estimateFormat.ts
  • src/lib/utils/getDesignateCtaState.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/lib/constants/apiRoutes.ts
  • src/lib/utils/estimateFormat.ts
  • src/lib/api/getApiError.ts
  • src/components/common/Modal/ModalDescription.tsx
  • src/lib/utils/getDesignateCtaState.ts
  • src/components/mover/detail/MoverDetailActions.tsx
  • src/components/common/Button/Button.tsx
  • src/components/common/Modal/ModalClose.tsx
  • src/components/common/Modal/ModalTitle.tsx
  • src/components/auth/LoginRequiredModal.tsx
  • src/components/auth/LoginRequiredModalProvider.tsx
  • src/components/estimate/EstimateRequestForm.tsx

Comment thread src/app/estimates/requests/page.tsx
Comment thread src/components/estimate/DesignateSuccessModal.tsx Outdated
Comment thread src/lib/api/estimateRequest.ts
@youngmis

Copy link
Copy Markdown
Contributor

검토 완료 후 1가지 수정 의견 인라인 코멘트 달아두었습니다. 확인부탁드립니다!

@juengseulki juengseulki left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📋 PR 리뷰

👍 좋았던 점

  • 지정 견적 요청 API 경로와 요청 body가 백엔드 구현과 일치합니다.
  • 서버가 반환하는 최신 견적 요청 데이터를 MyEstimateRequestItem으로 받아 ACTIVE 캐시에 즉시 반영해 성공 직후 CTA 상태가 자연스럽게 갱신됩니다.
  • 활성 견적 조회를 공통 훅으로 분리해 견적 요청 폼과 기사 상세 화면에서 같은 캐시와 API를 재사용할 수 있게 했습니다.
  • 클라이언트 게이트를 순수 함수로 분리해 MoverDetailView 내부의 조건문 복잡도를 낮췄습니다.
  • 프론트에서 상태·만료·중복·인원 제한을 미리 확인하지만 서버에서도 동일 조건을 최종 검증하므로, 클라이언트 데이터만 신뢰하는 구조는 아닙니다.
  • 활성 견적 조회 실패를 일반 견적 없음과 구분해 잘못된 안내 모달이 표시되지 않도록 처리한 점이 좋았습니다.
  • 비회원, 일반 견적 요청 없음, 지정 완료, 제한 초과 등 상황별로 Modal과 Toast를 구분해 사용자가 다음 행동을 이해하기 쉽게 구성했습니다.
  • 공통 Modal과 Button은 기본값을 유지하면서 필요한 variant와 disabled 스타일만 확장해 기존 사용처의 영향 범위를 줄였습니다.
  • 성공 모달에서 관련 화면으로 이동할 수 있도록 후속 행동까지 연결했습니다.

🚨 수정이 필요한 부분

인라인 코멘트로 아래 내용을 남기면 좋겠습니다.

  • 지정 요청 성공 시 ACTIVE와 MY_LIST만 갱신하고 있어, 실제 대기 중 견적 화면에서 사용하는 ESTIMATES.PENDING_LIST_ROOT에는 기존 designatedMovers 데이터가 남을 수 있습니다. 지정 성공 시 해당 캐시도 함께 invalidate해 주세요.

🔍 확인 및 제안

  • 활성 견적 조회 오류 후 버튼을 다시 눌러도 refetch가 실행되지 않아 재시도 안내와 실제 동작이 맞지 않을 수 있습니다. 클릭 시 재조회하거나 별도의 재시도 동작을 제공할지 확인 부탁드립니다.
  • 이미 지정된 기사 여부를 상태·만료보다 먼저 판단하고 있는데, 수정 불가능한 요청에서도 지정 견적 요청 완료를 우선 표시하려는 정책인지 확인하고 싶습니다.
  • To Reviewer에 문의하신 클라이언트 게이트 방식은 괜찮아 보입니다. 서버 검증을 대체하지 않고 UX 최적화 용도로만 사용하고 있어 현재 구조에 문제가 없습니다.
  • PR 설명에는 createEstimateRequest가 axios로 남아 있다고 적혀 있지만 최종 커밋에서는 해당 함수까지 fetchInstance로 전환되어 현재 estimateRequest.ts 내부는 통일된 상태입니다. PR 설명만 최신 상태에 맞게 수정하면 좋겠습니다.

전체적으로 지정 견적 요청의 성공 흐름뿐 아니라 비회원, 조회 실패, 활성 요청 없음, 중복 지정, 제한 초과까지 세분화해 연결한 점이 좋았습니다. 다만 지정 요청은 여러 화면에서 공유하는 견적 요청 데이터를 변경하므로, 대기 중 견적 목록 캐시까지 함께 갱신한 뒤 머지하는 것이 안전해 보입니다. 수고하셨습니다! 😊

Comment thread src/hooks/useDesignateMover.ts
Comment thread src/components/mover/detail/MoverDetailView.tsx
Comment thread src/lib/utils/getDesignateCtaState.ts Outdated

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

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

Inline comments:
In `@src/components/estimate/EstimateRequestForm.tsx`:
- Line 228: Update the cache write in the createEstimateRequest flow to store
only a MyEstimateRequestItem or null, never a boolean; replace the response ??
true fallback used with queryClient.setQueryData for
QUERY_KEYS.ESTIMATE_REQUESTS.ACTIVE with a null-compatible value that preserves
the useActiveEstimateRequest cache contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fa0af396-73ba-4eb9-a18e-91bb0833dd02

📥 Commits

Reviewing files that changed from the base of the PR and between a784866 and bc1948d.

📒 Files selected for processing (6)
  • src/app/estimates/requests/page.tsx
  • src/components/estimate/DesignateSuccessModal.tsx
  • src/components/estimate/EstimateRequestForm.tsx
  • src/components/mover/detail/MoverDetailView.tsx
  • src/hooks/useDesignateMover.ts
  • src/lib/api/estimateRequest.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/estimate/DesignateSuccessModal.tsx

Comment thread src/components/estimate/EstimateRequestForm.tsx Outdated
- 조회 실패 상태에서 재클릭 시 refetch 후 최신 결과로 CTA를 다시 판단
- 상태/만료를 해당 기사 지정 여부보다 먼저 검사
- 요청이 불가능한 경우 Toast 대신 버튼 비활성+라벨로 안내
onSuccessRef.current?.(data);
},
onError: (error) => {
onErrorRef.current?.(getApiErrorMessage(error, "지정 견적 요청에 실패했습니다."));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

서버가 요청을 거절하는 경우(이미 지정됨, 한도 초과 등) 지금은 토스트만 뜨고 ACTIVE 캐시가 그대로라, CTA가 계속 "요청 가능"으로 남아 같은 에러가 반복될 수 있을 것 같습니다.
이런 거절 케이스가 실제로 있다면 onError에서 ACTIVE를 invalidate해서 CTA를 실제 상태와 맞춰주는 것도 고려해보시면 좋을 것 같아요!

@wndnjs2037

Copy link
Copy Markdown
Collaborator

CTA 판정을 순수 함수로 분리하고 satisfies Record로 라벨 키 누락을 컴파일 타임에 잡히게 한 부분, /active가 미요청과 확정 이후를 구분하지 못하는 한계를 주석으로 명시해두신 부분이 잘 구현된 것 같습니다.

To Reviewer 내용 관련해서 말씀드리자면,
확정 요청 구분은 /active 응답만으로는 미요청과 확정 이후를 구분할 정보 자체가 없어서, 프론트에서 추측으로 나누면 잘못된 안내가 뜰 수 있습니다.
백엔드 응답에 종료 사유가 추가된 뒤에 처리하는 게 안전한 순서라 후속으로 두시면 될 것 같습니다.

이중 방어 구조는 클라이언트 게이트로 불필요한 요청을 줄이면서 최종 판단은 서버에 맡기는 형태라, 클라이언트 판단이 어긋나도 잘못된 요청이 서버를 통과하지 못합니다. 지금 구조 그대로 가시면 될 것 같습니다.

모달 이원화는 사용처가 2곳뿐이고 도메인이 달라서, 지금 합치면 두 화면의 요구가 갈릴 때마다 조건이 늘어나는 공통 컴포넌트가 되기 쉽습니다. 중복을 잠시 허용하고 패턴이 보일 때 통합하는 판단이 맞아 보입니다.

구현하느라 고생 많으셨습니다!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants