feat: 기사 상세 페이지 지정 견적 요청 기능 구현 - #30
Conversation
- Modal.Title·Desc에 variant/className prop 추가 - Modal.Close 사이즈 반응형 적용
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough활성 견적 조회와 기사 지정 API·훅을 추가하고, 기사 상세 CTA에서 로그인·일반 견적 요청·기사 지정 흐름을 상태에 따라 처리하도록 연결했습니다. 관련 모달, 버튼 스타일, 견적 폼 조회, 리뷰 날짜 표시와 견적 요청 목록 페이지도 변경했습니다. Changes지정 견적 요청
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: 캐시 갱신 및 성공 처리
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (19)
src/components/auth/LoginRequiredModal.tsxsrc/components/auth/LoginRequiredModalProvider.tsxsrc/components/common/Button/Button.tsxsrc/components/common/Modal/ModalClose.tsxsrc/components/common/Modal/ModalDescription.tsxsrc/components/common/Modal/ModalTitle.tsxsrc/components/estimate/EstimateRequestForm.tsxsrc/components/estimate/EstimateRequestRequiredModal.tsxsrc/components/mover/detail/MoverDetailActions.tsxsrc/components/mover/detail/MoverDetailReviews.tsxsrc/components/mover/detail/MoverDetailView.tsxsrc/hooks/useActiveEstimateRequest.tssrc/hooks/useDesignateMover.tssrc/lib/api/estimateRequest.tssrc/lib/api/getApiError.tssrc/lib/constants/apiRoutes.tssrc/lib/constants/appRoutes.tssrc/lib/utils/estimateFormat.tssrc/lib/utils/getDesignateCtaState.ts
344932c to
afb35a8
Compare
There was a problem hiding this comment.
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.ts의cn()을 사용해야 합니다. 정적 클래스라도 배열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
📒 Files selected for processing (21)
src/app/estimates/requests/page.tsxsrc/components/auth/LoginRequiredModal.tsxsrc/components/auth/LoginRequiredModalProvider.tsxsrc/components/common/Button/Button.tsxsrc/components/common/Modal/ModalClose.tsxsrc/components/common/Modal/ModalDescription.tsxsrc/components/common/Modal/ModalTitle.tsxsrc/components/estimate/DesignateSuccessModal.tsxsrc/components/estimate/EstimateRequestForm.tsxsrc/components/estimate/EstimateRequestRequiredModal.tsxsrc/components/mover/detail/MoverDetailActions.tsxsrc/components/mover/detail/MoverDetailReviews.tsxsrc/components/mover/detail/MoverDetailView.tsxsrc/hooks/useActiveEstimateRequest.tssrc/hooks/useDesignateMover.tssrc/lib/api/estimateRequest.tssrc/lib/api/getApiError.tssrc/lib/constants/apiRoutes.tssrc/lib/constants/appRoutes.tssrc/lib/utils/estimateFormat.tssrc/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
|
검토 완료 후 1가지 수정 의견 인라인 코멘트 달아두었습니다. 확인부탁드립니다! |
juengseulki
left a comment
There was a problem hiding this comment.
📋 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 설명만 최신 상태에 맞게 수정하면 좋겠습니다.
전체적으로 지정 견적 요청의 성공 흐름뿐 아니라 비회원, 조회 실패, 활성 요청 없음, 중복 지정, 제한 초과까지 세분화해 연결한 점이 좋았습니다. 다만 지정 요청은 여러 화면에서 공유하는 견적 요청 데이터를 변경하므로, 대기 중 견적 목록 캐시까지 함께 갱신한 뒤 머지하는 것이 안전해 보입니다. 수고하셨습니다! 😊
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
src/app/estimates/requests/page.tsxsrc/components/estimate/DesignateSuccessModal.tsxsrc/components/estimate/EstimateRequestForm.tsxsrc/components/mover/detail/MoverDetailView.tsxsrc/hooks/useDesignateMover.tssrc/lib/api/estimateRequest.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/estimate/DesignateSuccessModal.tsx
- 조회 실패 상태에서 재클릭 시 refetch 후 최신 결과로 CTA를 다시 판단
- 상태/만료를 해당 기사 지정 여부보다 먼저 검사 - 요청이 불가능한 경우 Toast 대신 버튼 비활성+라벨로 안내
| onSuccessRef.current?.(data); | ||
| }, | ||
| onError: (error) => { | ||
| onErrorRef.current?.(getApiErrorMessage(error, "지정 견적 요청에 실패했습니다.")); |
There was a problem hiding this comment.
서버가 요청을 거절하는 경우(이미 지정됨, 한도 초과 등) 지금은 토스트만 뜨고 ACTIVE 캐시가 그대로라, CTA가 계속 "요청 가능"으로 남아 같은 에러가 반복될 수 있을 것 같습니다.
이런 거절 케이스가 실제로 있다면 onError에서 ACTIVE를 invalidate해서 CTA를 실제 상태와 맞춰주는 것도 고려해보시면 좋을 것 같아요!
|
CTA 판정을 순수 함수로 분리하고 satisfies Record로 라벨 키 누락을 컴파일 타임에 잡히게 한 부분, /active가 미요청과 확정 이후를 구분하지 못하는 한계를 주석으로 명시해두신 부분이 잘 구현된 것 같습니다. To Reviewer 내용 관련해서 말씀드리자면, 이중 방어 구조는 클라이언트 게이트로 불필요한 요청을 줄이면서 최종 판단은 서버에 맡기는 형태라, 클라이언트 판단이 어긋나도 잘못된 요청이 서버를 통과하지 못합니다. 지금 구조 그대로 가시면 될 것 같습니다. 모달 이원화는 사용처가 2곳뿐이고 도메인이 달라서, 지금 합치면 두 화면의 요구가 갈릴 때마다 조건이 늘어나는 공통 컴포넌트가 되기 쉽습니다. 중복을 잠시 허용하고 패턴이 보일 때 통합하는 판단이 맞아 보입니다. 구현하느라 고생 많으셨습니다! |
📋 작업 내용
기사 상세 페이지에서 지정 견적 요청 기능을 구현했습니다.
🔥 변경 사항
API / 훅
지정 견적 요청 API 추가
designateMoverAPI 클라이언트 추가useDesignateMoverhook 추가: 지정 요청 mutation + 캐시 갱신기존 active 견적 API 변경
getActiveEstimateRequest를axiosInstance→fetchInstance로 전환fetchInstance가 내부에서 처리)unknown | null→MyEstimateRequestItem | null로 명확하게 변경useActiveEstimateRequesthook으로 분리해 공용화EstimateRequestForm내부에서useQuery로 직접 조회해, 해당 컴포넌트에서만 사용할 수 있었음EstimateRequestForm과MoverDetailView양쪽에서 재사용true)을 넣지 않도록 수정 (리뷰 반영)MyEstimateRequestItem을 그대로 저장하고, 없으면invalidate로 재조회CTA 상태 관리
getDesignateCtaState유틸 추가: GET /active 응답 데이터에 들어있는 요청이 지정 가능한지 클라이언트에서 먼저 검사하고, 서버에서 한 번 더 검증하는 이중 방어 구조needEstimateRequestnotEditablePENDING/OPEN이 아닌 경우expiredexpiresAt만 지남)alreadyDesignatedlimitExceededavailablenotEditable,expired은/active는 진행 중인 요청만 전달해주기 때문에 정상 흐름에서는 발생할 경우가 적지만, 캐시에 예전 active가 남아 있거나, 응답이 오는 사이에 만료/확정된 경우 등 예외적인 상황을 위한 방어 로직입니다.CTA의 목적을 고려해 상태와 만료 검사를
alreadyDesignated보다 먼저 수행하도록 수정 (리뷰 반영)기사 상세 페이지
MoverDetailView: 로그인 → 활성 견적 확인 → 지정 요청 전체 플로우 연결isError) 시 기존 CTA 처리를 막고, Toast로 재시도 안내 표시 (리뷰 반영)MoverDetailActions:requestDisabled,requestButtonLabelprop 추가EstimateRequestRequiredModal추가: 일반 견적 요청이 없을 때 안내DesignateSuccessModal추가: 지정 요청 완료 시 안내 + "지정 견적서 보러가기" 이동LoginRequiredModal에descriptionprop 추가: 호출 맥락에 따라 문구 변경 가능 (찜 / 지정 견적 등)refetch하도록 수정 (리뷰 반영)공통 컴포넌트 확장
Modal.Title:variant,classNameprop 추가Modal.Desc:variantprop 추가Modal.Close: 반응형 사이즈 적용 (24px/md:36px)Button: solid/outline disabled 스타일 추가기타
/estimates/requests: 일반 고객이 자신이 보낸 견적을 확인할 수 있는 페이지 추가, 임시 플레이스홀더 페이지✅ 체크리스트
📷 스크린샷 (선택)
기본
지정 견적 요청 처리 중
지정 견적 요청 완료
이미 해당 기사를 지정한 경우 (버튼 비활성화)
비회원 (모달)
활성 견적 요청이 없음 (모달)
기사 3명 초과함 (버튼 비활성화)
💬 To Reviewer
/activeAPI 응답을 수정하고자 합니다.getDesignateCtaState추가status,expiresAt,designatedMovers)를 읽어 먼저 차단합니다.Modal 관련 (별도 리팩토링 예정)
Modal.tsx와Modal/Modal.tsx가 혼재합니다. 아직 ui를 구현 중인 단계이며, 모달 디자인이 확정되지 않았음을 고려해 모달 통합은 별도 리팩토링으로 진행할 예정입니다.ModalMain수정이 필요한 영역이라 이번 PR 범위에서 제외했습니다.LoginRequiredModal과EstimateRequestRequiredModal에 동일한 패널 스타일 상수(PANEL_CLASSNAME)가 각각 로컬로 들어있습니다. 사용처가 2곳뿐이고 도메인(auth/estimate)이 달라 중복을 허용했습니다. (추상화 시 이점보다 비용이 더 들 것이라 예상) 모달 통합 시 함께 정리할 예정입니다.기타
/estimates/requests는 고객이 보낸 견적 요청을 확인하는 페이지로, 아직 구현 전인 임시 플레이스홀더입니다.Summary by CodeRabbit
Summary
새로운 기능
개선 사항
YYYY-MM-DD로 통일했습니다.