Conversation
|
Note
|
| 코호트 / 파일 | 요약 |
|---|---|
MySurvey 페이지 FAB 메뉴 src/features/mysurvey/pages/index.tsx |
기존 설문 추가 버튼을 framer-motion 기반 애니메이션 FAB 메뉴로 대체. 백드롭 오버레이, 상태 관리(isFabOpen), 네비게이션 핸들러(Google 양식 변환/일반 폼 생성) 추가. |
결제 상수 및 타입 src/features/payment/constants/payment.ts |
문항 수 범위를 나타내는 QuestionCountRange 타입("1~30" | "31~50")과 QUESTION_COUNT_OPTIONS 배열 추가. EstimateField 열거형에 QuestionCount 멤버 추가. |
결제 페이지 및 컴포넌트 src/features/payment/pages/EstimatePage.tsx, src/features/payment/components/estimate/EstimateDetailBottomSheet.tsx |
결제 페이지에 문항 수 필드 선택 UI 추가. 하단 시트의 items 배열을 간단한 rows 배열로 재구성하고, 성별/연령대 개별 가격 표시 제거 및 targetingSummary 통합. |
결제 컨텍스트 src/shared/contexts/PaymentContext.tsx |
Estimate 타입에 questionCount 필드 추가("1~30" 기본값). 초기 상태 및 리셋 로직에 반영. |
가격 계산 핵심 로직 src/shared/lib/estimatePrice.ts, src/shared/lib/paymentCalculator.ts, src/shared/lib/estimatePricingTable.ts (신규) |
기존 개별 수수료 계산 함수 제거. 테이블 기반 조회 시스템 도입: 참여자 규모, 문항 수, 타겟팅 프리셋을 조합하여 ESTIMATE_PRICE_TABLE에서 가격 조회. PriceBreakdown 구조 단순화 (개별 가격 필드 제거, targetingSummary 추가). |
Sequence Diagram(s)
sequenceDiagram
participant User as 사용자
participant FAB as FAB 메뉴
participant Router as 라우터
participant EstimatePage as 결제 페이지
User->>FAB: FAB 토글 버튼 클릭
activate FAB
FAB->>FAB: isFabOpen 상태 전환
FAB->>User: 메뉴 애니메이션 표시
deactivate FAB
User->>FAB: "Google 양식 변환" 또는 "설문 생성" 선택
activate FAB
FAB->>Router: 네비게이션 (state.source 설정)
Router->>EstimatePage: 라우팅 + source 파라미터 전달
EstimatePage->>EstimatePage: 문항 수, 참여자 규모, 타겟팅 정보 입력
EstimatePage->>EstimatePage: lookupEstimateTablePrice()로 예상 가격 계산
EstimatePage->>User: 예상 가격 표시
deactivate FAB
sequenceDiagram
participant User as 사용자
participant EstimatePage as 결제 페이지
participant PaymentContext as 결제 컨텍스트
participant PricingTable as 가격 테이블 모듈
User->>EstimatePage: 문항 수, 참여자, 성별/연령대 선택
EstimatePage->>PaymentContext: estimate 상태 업데이트<br/>(questionCount, desiredParticipants, gender, ages)
PaymentContext->>PricingTable: lookupEstimateTablePrice(estimate) 호출
activate PricingTable
PricingTable->>PricingTable: parseParticipantTier()<br/>(참여자 규모 → 구간 매핑)
PricingTable->>PricingTable: getEstimateTargetingPreset()<br/>(성별/연령대 → 타겟팅 프리셋)
PricingTable->>PricingTable: ESTIMATE_PRICE_TABLE 조회<br/>[구간][문항범위][프리셋]
deactivate PricingTable
PricingTable-->>EstimatePage: 예상 가격 반환
EstimatePage->>User: 계산된 가격 및 타겟팅 요약 표시
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~45분
Possibly related PRs
- [SUR-58] 로깅 추가 #114: FAB 네비게이션이
location.state.source("mysurvey_fab"/"mysurvey_button")를 설정하는 로직을 추가하며, 이는 이 PR의 FAB 메뉴 라우팅과 직접 연결됨. - [SUR-58] 각 기능 로깅 추가 #106: MySurvey 네비게이션 상태 처리와 라우터 state의 source 값 확장을 다루고 있어 FAB 메뉴 추가와 동일한 경로 관련.
- [ON-218] 온서베이 디자인 시스템 전체 변경 #109: 결제 시스템 및 가격 계산 로직(
estimatePrice,paymentCalculator, estimate 관련 페이지/컴포넌트)을 수정하는 것과 같은 영역에서의 변경.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | PR 제목은 견적 계산기 가격 구조 변경이라는 주요 변경사항을 명확하게 설명하고 있으며, 실제 변경 내용과 일치합니다. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
✏️ 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
OMF-279
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.
Comment @coderabbitai help to get the list of available commands and usage tips.
| <span | ||
| className={`${FAB_SIZE_CLASS} inline-flex items-center justify-center rounded-full! bg-[#191F28]`} |
There was a problem hiding this comment.
bg-[#191F28] 고정값으로 들어가 있는데 이 컬러값을 토큰으로 관리해도 좋을 것 같습니다
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/features/payment/pages/EstimatePage.tsx (1)
51-61:desiredParticipants초기값 불일치
PaymentContext에서는desiredParticipants를"50명"으로 초기화하지만, 여기서는"50"으로 설정합니다.parseParticipantTier는 숫자만 추출하므로 기능상 문제는 없지만, 일관성을 위해 값 형식을 통일하는 것이 좋습니다.♻️ 일관성 개선 제안
handleEstimateChange({ ...estimate, - desiredParticipants: "50", + desiredParticipants: "50명", gender: "ALL", ages: ["ALL"], location: "ALL", questionCount: estimate.questionCount ?? "1~30", });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/payment/pages/EstimatePage.tsx` around lines 51 - 61, The default desiredParticipants value set in this component is "50" but PaymentContext uses "50명"; update the initialization in the useEffect (where estimate.desiredParticipants is checked and handleEstimateChange is called) to use the same string format ("50명") so it matches PaymentContext and parseParticipantTier expectations; ensure you update the value in the handleEstimateChange payload (and any nearby references to estimate.desiredParticipants) to maintain consistent formatting across the app.src/shared/lib/estimatePricingTable.ts (1)
152-161: 가격 조회 실패 시 0원 반환의 잠재적 문제
lookupEstimateTablePrice가 유효하지 않은 응답자 수로 인해 조회 실패 시 조용히0을 반환합니다. 이로 인해 사용자에게 "0원"이 표시될 수 있으며, 이는 혼란을 줄 수 있습니다.운영 환경에서 이 상황이 발생하면 추적이 어려울 수 있으므로, 코딩 가이드라인에 따라 예외 상황을 로깅하는 것을 고려해 주세요.
♻️ 로깅 추가 제안
+import * as Sentry from "@sentry/react"; + // 견적서 표 기준 총액 (원). 조합이 표에 없으면 0. export const lookupEstimateTablePrice = (estimate: Estimate): number => { const tier = parseParticipantTier(estimate.desiredParticipants); - if (!tier) return 0; + if (!tier) { + const { logger } = Sentry; + logger.warn( + logger.fmt`Invalid participant tier for desiredParticipants=${estimate.desiredParticipants}`, + ); + return 0; + } const q = estimate.questionCount; const preset = getEstimateTargetingPreset(estimate); return ESTIMATE_PRICE_TABLE[tier][q][preset] ?? 0; };As per coding guidelines, "Use
logger.fmttemplate literal function to bring variables into structured logs for better log formatting and variable interpolation" 및 "Use appropriate logger methods based on severity:logger.warn()".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/lib/estimatePricingTable.ts` around lines 152 - 161, lookupEstimateTablePrice silently returns 0 when the ESTIMATE_PRICE_TABLE lookup fails which can mask misconfiguration; update lookupEstimateTablePrice to detect a missing entry (after using parseParticipantTier and getEstimateTargetingPreset) and call logger.warn(logger.fmt`Missing price for tier=${tier} questionCount=${q} preset=${preset}`) before returning 0 so failures are recorded; reference the lookup in ESTIMATE_PRICE_TABLE[tier][q][preset] and use logger.fmt/ logger.warn for structured logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/features/mysurvey/pages/index.tsx`:
- Around line 44-48: The navigate call in goToGoogleFormConversion sets state {
source: "mysurvey_fab" } but that state is never read or forwarded by
GoogleFormConversionLandingPage (GoogleFormConversionLandingPage and its
subsequent navigation to /payment/google-form-conversion), so the source is
lost; either read location.state.source in GoogleFormConversionLandingPage (use
the component that consumes location.state) and re-dispatch it when navigating
to /payment/google-form-conversion, or emit the analytics/event immediately in
goToGoogleFormConversion instead of relying on navigation state—update the
GoogleFormConversionLandingPage to accept and forward location.state (or to call
the analytics util) and ensure navigate(...) to /payment/google-form-conversion
includes the same state if forwarding.
- Around line 69-79: The backdrop currently renders as motion.button
(key="fab-backdrop") which creates a hidden focusable control; change it to
motion.div (preserve className, initial/animate/exit/transition props and
onClick that calls setIsFabOpen(false)) so it is not focusable, or if you must
keep a button keep key="fab-backdrop" but add tabIndex={-1} to remove it from
the tab order; ensure you remove the type="button" prop when switching to
motion.div and retain aria-hidden if still desired.
- Around line 44-54: The three click handlers (goToGoogleFormConversion,
goToCreateSurvey and the FAB background-close / toggle handlers) must be
instrumented with Sentry traces: import Sentry if not present, wrap each handler
body in a Sentry.startSpan({ op: "ui.click", description: "<handler name>" })
call, perform the existing actions (setIsFabOpen, navigate) inside the span,
call span.finish() after navigation (or in a finally block) and ensure any
thrown errors are rethrown or handled; use the handler names
(goToGoogleFormConversion, goToCreateSurvey, and the background/FAB toggle
functions) as the span description to help identify the traces.
---
Nitpick comments:
In `@src/features/payment/pages/EstimatePage.tsx`:
- Around line 51-61: The default desiredParticipants value set in this component
is "50" but PaymentContext uses "50명"; update the initialization in the
useEffect (where estimate.desiredParticipants is checked and
handleEstimateChange is called) to use the same string format ("50명") so it
matches PaymentContext and parseParticipantTier expectations; ensure you update
the value in the handleEstimateChange payload (and any nearby references to
estimate.desiredParticipants) to maintain consistent formatting across the app.
In `@src/shared/lib/estimatePricingTable.ts`:
- Around line 152-161: lookupEstimateTablePrice silently returns 0 when the
ESTIMATE_PRICE_TABLE lookup fails which can mask misconfiguration; update
lookupEstimateTablePrice to detect a missing entry (after using
parseParticipantTier and getEstimateTargetingPreset) and call
logger.warn(logger.fmt`Missing price for tier=${tier} questionCount=${q}
preset=${preset}`) before returning 0 so failures are recorded; reference the
lookup in ESTIMATE_PRICE_TABLE[tier][q][preset] and use logger.fmt/ logger.warn
for structured logging.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7ebb5cb2-3a73-4d01-9400-759e02944ac5
⛔ Files ignored due to path filters (4)
src/assets/mysurveyFloating/icon-clip.svgis excluded by!**/*.svgsrc/assets/mysurveyFloating/icon-document-lines-mono-docs.svgis excluded by!**/*.svgsrc/assets/mysurveyFloating/icon-navigation-x-mono.svgis excluded by!**/*.svgsrc/assets/mysurveyFloating/icon-plus-btn.svgis excluded by!**/*.svg
📒 Files selected for processing (8)
src/features/mysurvey/pages/index.tsxsrc/features/payment/components/estimate/EstimateDetailBottomSheet.tsxsrc/features/payment/constants/payment.tssrc/features/payment/pages/EstimatePage.tsxsrc/shared/contexts/PaymentContext.tsxsrc/shared/lib/estimatePrice.tssrc/shared/lib/estimatePricingTable.tssrc/shared/lib/paymentCalculator.ts
| const goToGoogleFormConversion = () => { | ||
| setIsFabOpen(false); | ||
| navigate("/google-form-conversion-landing", { | ||
| state: { source: "mysurvey_fab" }, | ||
| }); |
There was a problem hiding this comment.
mysurvey_fab source가 현재 전환 흐름에서 유실됩니다.
src/features/google-form-conversion/pages/GoogleFormConversionLandingPage.tsx:1-35가 location.state를 읽지 않고, 다음 /payment/google-form-conversion 이동에도 상태를 넘기지 않아서 여기서 넣은 source 값은 어디에서도 소비되지 않습니다. 유입 출처를 보려는 의도였다면 랜딩 페이지에서 읽어 재전달하거나, 이 지점에서 바로 이벤트를 남기도록 맞춰 두는 편이 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/mysurvey/pages/index.tsx` around lines 44 - 48, The navigate
call in goToGoogleFormConversion sets state { source: "mysurvey_fab" } but that
state is never read or forwarded by GoogleFormConversionLandingPage
(GoogleFormConversionLandingPage and its subsequent navigation to
/payment/google-form-conversion), so the source is lost; either read
location.state.source in GoogleFormConversionLandingPage (use the component that
consumes location.state) and re-dispatch it when navigating to
/payment/google-form-conversion, or emit the analytics/event immediately in
goToGoogleFormConversion instead of relying on navigation state—update the
GoogleFormConversionLandingPage to accept and forward location.state (or to call
the analytics util) and ensure navigate(...) to /payment/google-form-conversion
includes the same state if forwarding.
| const goToGoogleFormConversion = () => { | ||
| setIsFabOpen(false); | ||
| navigate("/google-form-conversion-landing", { | ||
| state: { source: "mysurvey_fab" }, | ||
| }); | ||
| }; | ||
|
|
||
| const handleAddSurvey = () => | ||
| const goToCreateSurvey = () => { | ||
| setIsFabOpen(false); | ||
| navigate("/createFormStart", { state: { source: "mysurvey_button" } }); | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && find . -name "index.tsx" -path "*/features/mysurvey/pages/*" 2>/dev/null | head -5Repository: On-Survey/Frontend
Length of output: 44
🏁 Script executed:
git ls-files "src/features/mysurvey/pages/index.tsx"Repository: On-Survey/Frontend
Length of output: 99
🏁 Script executed:
wc -l src/features/mysurvey/pages/index.tsxRepository: On-Survey/Frontend
Length of output: 103
🏁 Script executed:
cat -n src/features/mysurvey/pages/index.tsxRepository: On-Survey/Frontend
Length of output: 5292
새로 추가한 FAB 클릭 핸들러들에 Sentry 계측이 필요합니다.
이 컴포넌트의 세 가지 클릭 핸들러(goToGoogleFormConversion, goToCreateSurvey, 배경 닫기, FAB 토글)가 모두 Sentry.startSpan으로 감싸져 있지 않습니다. 저장소 가이드라인에 따라 컴포넌트 액션 핸들러는 Sentry.startSpan({ op: "ui.click", ... })을 사용하여 클릭 trace를 기록해야 합니다.
적용 예시
+import * as Sentry from "@sentry/react";
import { BottomNavigation } from "@shared/components/BottomNavigation";
// ... other imports
const goToGoogleFormConversion = () => {
- setIsFabOpen(false);
- navigate("/google-form-conversion-landing", {
- state: { source: "mysurvey_fab" },
- });
+ Sentry.startSpan(
+ { op: "ui.click", name: "MySurvey FAB open google-form-conversion" },
+ (span) => {
+ span.setAttribute("destination", "/google-form-conversion-landing");
+ setIsFabOpen(false);
+ navigate("/google-form-conversion-landing", {
+ state: { source: "mysurvey_fab" },
+ });
+ },
+ );
};
const goToCreateSurvey = () => {
- setIsFabOpen(false);
- navigate("/createFormStart", { state: { source: "mysurvey_button" } });
+ Sentry.startSpan(
+ { op: "ui.click", name: "MySurvey FAB open create-survey" },
+ (span) => {
+ span.setAttribute("destination", "/createFormStart");
+ setIsFabOpen(false);
+ navigate("/createFormStart", { state: { source: "mysurvey_button" } });
+ },
+ );
};
- onClick={() => setIsFabOpen(false)}
+ onClick={() =>
+ Sentry.startSpan({ op: "ui.click", name: "MySurvey FAB backdrop close" }, () => {
+ setIsFabOpen(false);
+ })
+ }
- onClick={() => setIsFabOpen((o) => !o)}
+ onClick={() =>
+ Sentry.startSpan(
+ { op: "ui.click", name: "MySurvey FAB toggle" },
+ (span) => {
+ span.setAttribute("next_state", !isFabOpen);
+ setIsFabOpen((o) => !o);
+ },
+ )
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/mysurvey/pages/index.tsx` around lines 44 - 54, The three click
handlers (goToGoogleFormConversion, goToCreateSurvey and the FAB
background-close / toggle handlers) must be instrumented with Sentry traces:
import Sentry if not present, wrap each handler body in a Sentry.startSpan({ op:
"ui.click", description: "<handler name>" }) call, perform the existing actions
(setIsFabOpen, navigate) inside the span, call span.finish() after navigation
(or in a finally block) and ensure any thrown errors are rethrown or handled;
use the handler names (goToGoogleFormConversion, goToCreateSurvey, and the
background/FAB toggle functions) as the span description to help identify the
traces.
| <motion.button | ||
| type="button" | ||
| key="fab-backdrop" | ||
| className="fixed inset-0 z-40 bg-black/65" | ||
| initial={{ opacity: 0 }} | ||
| animate={{ opacity: 1 }} | ||
| exit={{ opacity: 0 }} | ||
| transition={{ duration: 0.2 }} | ||
| aria-hidden | ||
| onClick={() => setIsFabOpen(false)} | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and read the specific file and lines
cat -n src/features/mysurvey/pages/index.tsx | sed -n '60,85p'Repository: On-Survey/Frontend
Length of output: 925
🏁 Script executed:
# Check the full context of the file to understand component structure
wc -l src/features/mysurvey/pages/index.tsxRepository: On-Survey/Frontend
Length of output: 103
🏁 Script executed:
# Search for similar motion.button patterns in the codebase
rg "motion\.button" -A 5 -B 2 --max-count=10Repository: On-Survey/Frontend
Length of output: 6394
🏁 Script executed:
# Check for aria-hidden usage patterns with interactive elements
rg "aria-hidden" -B 3 -A 3 --max-count=15Repository: On-Survey/Frontend
Length of output: 50374
배경 레이어를 aria-hidden인 button으로 두면 숨겨진 포커스 대상이 생깁니다.
aria-hidden은 접근성 트리에서만 숨기고 탭 포커스는 막지 않아서, 키보드 사용자나 스크린리더 사용자가 불일치하는 경험을 할 수 있습니다. 이 레이어는 motion.div로 바꾸거나 최소한 tabIndex={-1}을 추가해 포커스 대상에서 제외해 주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/mysurvey/pages/index.tsx` around lines 69 - 79, The backdrop
currently renders as motion.button (key="fab-backdrop") which creates a hidden
focusable control; change it to motion.div (preserve className,
initial/animate/exit/transition props and onClick that calls
setIsFabOpen(false)) so it is not focusable, or if you must keep a button keep
key="fab-backdrop" but add tabIndex={-1} to remove it from the tab order; ensure
you remove the type="button" prop when switching to motion.div and retain
aria-hidden if still desired.
📌 주요 변경 사항
🔗 관련 이슈
Jira 링크: https://onsurvey.atlassian.net/browse/OMF-279
✅ 리뷰 요청 사항 (Need Review)
🎨 스크린샷 (선택)
Summary by CodeRabbit
릴리스 노트
New Features
Refactor