Skip to content

[OMF-258] 문항 수에 따른 프로모션 가격 변경 UI 수정#146

Merged
chldsbdud merged 5 commits intomainfrom
OMF-258
Mar 12, 2026
Merged

[OMF-258] 문항 수에 따른 프로모션 가격 변경 UI 수정#146
chldsbdud merged 5 commits intomainfrom
OMF-258

Conversation

@chldsbdud
Copy link
Collaborator

@chldsbdud chldsbdud commented Mar 10, 2026

📌 주요 변경 사항

  • 프로모션 가격 300/500 변경에 따른 UI 수정
  • 문항 없는 섹션일 때도 타이틀/ 설명 표시
  • 참여 보상에 따른 소요시간 출력 -> 500원 4분

🔗 관련 이슈

Jira 링크: https://onsurvey.atlassian.net/browse/OMF-258, https://onsurvey.atlassian.net/browse/OMF-256

✅ 리뷰 요청 사항 (Need Review)

  • 🙂 크게 우려되는 사항은 없어요. 가볍게 리뷰 부탁드려요.

🎨 스크린샷 (선택)

페이지 1 페이지 2

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 설문 목록 및 긴급 설문에 보상금액을 동적으로 표시합니다. 금액이 없으면 기본값이 적용됩니다.
    • 설문 카드에 금액 기반 예상 소요시간을 함께 노출합니다.
    • 설문 완료 화면의 획득 보상액 및 트래킹이 동적으로 반영됩니다.
  • Chores

    • 집계 로직이 개별 설문 금액을 반영하도록 개선되어 총 프로모션 합계가 정확해졌습니다.

@chldsbdud chldsbdud self-assigned this Mar 10, 2026
@chldsbdud chldsbdud added the ✨Feat 새로운 기능 추가 label Mar 10, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'project', 'rules', 'files', 'ignore'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

설문 보상 가격을 하드코딩된 200원에서 설문별 price 값으로 동적으로 처리하도록 변경; 가격 전파를 위해 타입·훅·컴포넌트·네비게이션·완료 페이지 전반에서 price 필드가 추가 및 사용됨.

Changes

Cohort / File(s) Summary
설문 목록 UI
src/features/survey-list/components/CustomSurveyList.tsx, src/features/survey-list/components/SurveyList.tsx, src/features/survey-list/components/UrgentSurveyList.tsx
설문 아이템의 보상 표시를 고정값에서 survey.price ?? 200 형태로 변경하여 동적 가격 노출.
설문 목록 훅·페이지
src/features/survey-list/hooks/useProcessedOngoingSurveys.ts, src/features/survey-list/pages/SurveyList.tsx
mapSurveyToItemprice 추가. totalPromotionAmount 계산을 설문별 가격 맵(기본 200)으로 합산하도록 재구성.
타입 정의 (목록/서비스)
src/features/survey-list/service/surveyList/types.ts, src/shared/types/surveyList.ts, src/features/survey/service/surveyParticipation/types.ts
OngoingSurveySummary, SurveyListItem, SurveyBasicInfo 등에 선택적 price?: number 필드 추가.
설문 보상 컴포넌트
src/features/survey/components/SurveyRewardInfoCard.tsx
price prop 및 DEFAULT_REWARD_PRICE 도입. getEstimatedTimeByPrice 내보내기 추가; 보상가격 기반 예상시간 계산 로직으로 변경 및 UI에 동적 보상가 표시.
설문 네비게이션 · 페이지 흐름
src/features/survey/hooks/useSurveyNavigation.ts, src/features/survey/pages/SectionBasedSurvey.tsx, src/features/survey/pages/Survey.tsx, src/features/survey/pages/Complete.tsx
surveyInfo를 외부 스코프로 유지하여 priceisFree를 네비게이션 state로 전달. Complete 페이지에서 reward_amount 및 추적 이벤트에 price 반영; SectionBasedSurvey에서 질문 없을 때 조기 반환 제거(렌더 허용).

Sequence Diagram

sequenceDiagram
    participant User as 사용자
    participant SurveyPage as 설문 페이지
    participant Service as 설문 서비스
    participant Navigation as 네비게이션
    participant Complete as 완료 페이지

    User->>SurveyPage: 설문 시작
    SurveyPage->>Service: getSurveyInfo(surveyId)
    Service-->>SurveyPage: surveyInfo { price, isFree, ... }
    SurveyPage->>SurveyPage: SurveyRewardInfoCard에 price 전달 (동적 표시)
    User->>SurveyPage: 설문 완료
    SurveyPage->>Navigation: navigate(Complete, { price: surveyInfo?.price, isFree: surveyInfo?.isFree })
    Navigation->>Complete: location state 전달
    Complete->>Complete: reward_amount = locationState?.price ?? 200
    Complete->>Complete: 완료 추적 이벤트에 rewardAmount 포함
    Complete-->>User: 완료 화면 표시
Loading

예상 코드 리뷰 시간

🎯 3 (Moderate) | ⏱️ ~20 minutes

관련 PR

🚥 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 제목이 변경 사항의 핵심을 명확하게 요약하고 있습니다. 프로모션 가격 변경 UI 수정이라는 주요 목표를 직접적으로 반영하고 있으며, 기술 세부사항과 파일 목록 없이 간결하게 표현되어 있습니다.
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 (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch OMF-258

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

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

Caution

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

⚠️ Outside diff range comments (3)
src/features/survey/pages/SectionBasedSurvey.tsx (1)

321-338: ⚠️ Potential issue | 🔴 Critical

완료 화면 state에 지급 결과를 안 넘겨서 프로모션이 두 번 시도됩니다.

src/features/survey/pages/Complete.tsxpromotionIssued !== true면 재시도를 시작합니다. 여기서는 issuePromotion()이 성공해도 promotionIssued를 넘기지 않아서 유료 설문이 항상 재시도 경로로 들어가고, 지급 API가 비멱등이면 중복 지급까지 가능합니다.

수정 예시
 let surveyInfo: Awaited<ReturnType<typeof getSurveyInfo>> | undefined;
+let promotionIssued: boolean | undefined;
 try {
 	surveyInfo = await getSurveyInfo(surveyId);
 	if (!surveyInfo.isFree) {
 		await issuePromotion({ surveyId });
 	}
+	promotionIssued = true;
 	// 무료/유료 구분 없이 홈 설문 리스트 갱신
 	queryClient.invalidateQueries({ queryKey: ["allOngoingSurveys"] });
 } catch {
+	promotionIssued = false;
 	// 실패해도 완료 페이지 이동
 }
 
 navigate("/survey/complete", {
 	state: {
 		surveyId: String(surveyId),
+		isFree: surveyInfo?.isFree,
 		source: locationState?.source,
+		promotionIssued,
 		price: surveyInfo?.price,
 	},
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/survey/pages/SectionBasedSurvey.tsx` around lines 321 - 338, The
complete page navigation never passes whether promotion was issued, causing
Complete.tsx to retry issues; modify the flow around
getSurveyInfo/issuePromotion to track success and pass promotionIssued in
navigate state: declare a local boolean (e.g., promotionIssued = false), set it
to true only after await issuePromotion({ surveyId }) succeeds, and in the
navigate("/survey/complete", { state: { surveyId: String(surveyId), source:
locationState?.source, price: surveyInfo?.price, promotionIssued } }) include
that flag so Complete.tsx can skip retrying when true; ensure promotionIssued
remains false in the catch/failure paths.
src/features/survey/pages/Complete.tsx (1)

25-31: ⚠️ Potential issue | 🟠 Major

완료 이벤트가 무료/가격 누락 설문을 200원으로 오집계합니다.

price가 optional인데 여기서 ?? 200을 쓰면 무료 설문과 getSurveyInfo 실패 경로가 모두 실제 200원 설문으로 기록됩니다. isFree === true0을 보내고, 그 외에는 실제 price를 확보했을 때만 넣는 쪽이 안전합니다.

Also applies to: 57-69

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

In `@src/features/survey/pages/Complete.tsx` around lines 25 - 31, The page reads
location.state (locationState) and currently uses `price ?? 200`, which
misrecords free surveys and error paths as 200; change both occurrences (the
initial locationState handling and the later block around lines 57-69) so that
if locationState.isFree === true you send price = 0, otherwise only include
price when locationState.price is explicitly provided (omit the price field
entirely if undefined) — i.e., replace the `?? 200` logic with a conditional
that sets 0 for isFree, uses the provided price when present, and avoids
defaulting to 200.
src/features/survey-list/hooks/useProcessedOngoingSurveys.ts (1)

73-87: ⚠️ Potential issue | 🟠 Major

총 프로모션 금액 계산이 반환 리스트와 어긋납니다.

위에서 filteredRecommended/filteredImpending로 마감 설문을 걸러냈는데, 총액은 다시 원본 배열을 순회합니다. 그래서 화면에 안 보이는 설문과 isFree 설문(price 없음)까지 200원으로 합산됩니다.

수정 예시
-		const uniqueSurveyIds = getUniqueSurveyIdsFromArrays(
-			result.recommended,
-			result.impending,
-		);
+		const uniqueSurveyIds = getUniqueSurveyIdsFromArrays(
+			filteredRecommended,
+			filteredImpending,
+		);
 		const surveyIdToPrice = new Map<number, number>();
-		for (const s of [
-			...(result.recommended ?? []),
-			...(result.impending ?? []),
-		]) {
-			surveyIdToPrice.set(s.surveyId, s.price ?? 200);
+		for (const s of [...filteredRecommended, ...filteredImpending]) {
+			surveyIdToPrice.set(s.surveyId, s.isFree ? 0 : s.price ?? 200);
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/survey-list/hooks/useProcessedOngoingSurveys.ts` around lines 73
- 87, The totalAmount computation in useProcessedOngoingSurveys currently
iterates over the original result.recommended/result.impending arrays and uses
price fallback for surveys that were filtered out; update it to use the
already-filtered lists: build uniqueSurveyIds from filteredRecommended and
filteredImpending, populate surveyIdToPrice from those filtered arrays (so
excluded or isFree surveys are not counted), and then compute totalAmount by
reducing over the uniqueSurveyIds using surveyIdToPrice (with the same 200
fallback only for filtered items missing price). Ensure you update the
references to uniqueSurveyIds, surveyIdToPrice, and totalAmount accordingly.
🧹 Nitpick comments (2)
src/features/survey/hooks/useSurveyNavigation.ts (1)

217-260: 새 예외 경로를 콘솔 로그만으로 끝내지 마세요.

여기서 추가된 getSurveyInfo/issuePromotion 실패는 보상 지급 재시도까지 이어지는 경로인데, 현재는 console.error만 남겨서 운영 추적이 어렵습니다. Sentry.captureException(error)const { logger } = Sentry 기반 구조화 로그로 바꿔 주세요. As per coding guidelines, **/*.{js,jsx,ts,tsx}: Use `Sentry.captureException(error)` to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected`.

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

In `@src/features/survey/hooks/useSurveyNavigation.ts` around lines 217 - 260,
Replace the raw console error handling in the getSurveyInfo/issuePromotion
try-catch paths by capturing exceptions in Sentry and emitting structured logs:
import Sentry, call Sentry.captureException(error) inside each catch (both the
inner catch around issuePromotion and the outer catch around getSurveyInfo), and
also use the Sentry logger (const { logger } = Sentry) to emit a contextual
logger.error including identifiers like surveyId, promotionIssued, and a short
message (e.g., "issuePromotion failed" or "getSurveyInfo failed") instead of
console.error; keep promotionIssued assignment logic but ensure errors are
captured and logged via Sentry for observability.
src/features/survey/pages/SectionBasedSurvey.tsx (1)

321-331: 제출 액션의 예외/지연 경로를 Sentry로 남겨 주세요.

이 핸들러는 클릭 이후 getSurveyInfo/issuePromotion을 호출하지만 catch가 비어 있어서, 완료 페이지 fallback이 왜 실행됐는지 추적할 단서가 없습니다. Sentry.startSpan({ op: "ui.click", ... })로 제출 경로를 감싸고 catch에서 Sentry.captureException(error)를 남겨 주세요. As per coding guidelines, "Use Sentry.captureException(error) to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected" and "In component action handlers, use Sentry.startSpan with op: 'ui.click' to create spans for button clicks and other UI interactions".

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

In `@src/features/survey/pages/SectionBasedSurvey.tsx` around lines 321 - 331,
Wrap the submission flow that calls getSurveyInfo and issuePromotion in a Sentry
span using Sentry.startSpan({ op: "ui.click", description: "submit survey" })
and ensure the span is finished after the flow (success or failure); inside the
try block keep the existing calls (getSurveyInfo(surveyId), issuePromotion({
surveyId }), queryClient.invalidateQueries(...)) and in the catch block call
Sentry.captureException(error) before performing the existing fallback
navigation, so exceptions are recorded and the span is closed on both paths.
🤖 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/survey-list/components/SurveyList.tsx`:
- Around line 77-80: The list view is hardcoding "3분", causing mismatch with the
detail view's computation; extract or reuse the reward-to-duration logic from
SurveyRewardInfoCard (export its calculation function, e.g., getEstimatedMinutes
or calculateEstimatedMinutes) and import it into SurveyList, then replace the
hardcoded "3분" branch so bottom uses survey.isFree ? "보상이 없어요" :
`${<sharedDurationFunction>(survey.price ?? 200)}분이면 ${survey.price ?? 200}원
획득`; ensure the shared function name you export/import matches the symbol you
add in SurveyRewardInfoCard.

In `@src/features/survey/hooks/useSurveyNavigation.ts`:
- Around line 216-220: The Complete screen sometimes loses the isFree flag
because handlePrev() and the navigation to Complete don't include
surveyInfo.isFree in the navigation state; update the code paths that call
navigate/replace to the Complete screen (the block after awaiting getSurveyInfo
and the code around the handlePrev logic referenced near the 262-269 area) to
include isFree: surveyInfo?.isFree (or Boolean(surveyInfo?.isFree)) in the
location/state object, and ensure any re-checks that re-fetch surveyInfo also
propagate surveyInfo.isFree into locationState so locationState?.isFree is never
left undefined.

---

Outside diff comments:
In `@src/features/survey-list/hooks/useProcessedOngoingSurveys.ts`:
- Around line 73-87: The totalAmount computation in useProcessedOngoingSurveys
currently iterates over the original result.recommended/result.impending arrays
and uses price fallback for surveys that were filtered out; update it to use the
already-filtered lists: build uniqueSurveyIds from filteredRecommended and
filteredImpending, populate surveyIdToPrice from those filtered arrays (so
excluded or isFree surveys are not counted), and then compute totalAmount by
reducing over the uniqueSurveyIds using surveyIdToPrice (with the same 200
fallback only for filtered items missing price). Ensure you update the
references to uniqueSurveyIds, surveyIdToPrice, and totalAmount accordingly.

In `@src/features/survey/pages/Complete.tsx`:
- Around line 25-31: The page reads location.state (locationState) and currently
uses `price ?? 200`, which misrecords free surveys and error paths as 200;
change both occurrences (the initial locationState handling and the later block
around lines 57-69) so that if locationState.isFree === true you send price = 0,
otherwise only include price when locationState.price is explicitly provided
(omit the price field entirely if undefined) — i.e., replace the `?? 200` logic
with a conditional that sets 0 for isFree, uses the provided price when present,
and avoids defaulting to 200.

In `@src/features/survey/pages/SectionBasedSurvey.tsx`:
- Around line 321-338: The complete page navigation never passes whether
promotion was issued, causing Complete.tsx to retry issues; modify the flow
around getSurveyInfo/issuePromotion to track success and pass promotionIssued in
navigate state: declare a local boolean (e.g., promotionIssued = false), set it
to true only after await issuePromotion({ surveyId }) succeeds, and in the
navigate("/survey/complete", { state: { surveyId: String(surveyId), source:
locationState?.source, price: surveyInfo?.price, promotionIssued } }) include
that flag so Complete.tsx can skip retrying when true; ensure promotionIssued
remains false in the catch/failure paths.

---

Nitpick comments:
In `@src/features/survey/hooks/useSurveyNavigation.ts`:
- Around line 217-260: Replace the raw console error handling in the
getSurveyInfo/issuePromotion try-catch paths by capturing exceptions in Sentry
and emitting structured logs: import Sentry, call Sentry.captureException(error)
inside each catch (both the inner catch around issuePromotion and the outer
catch around getSurveyInfo), and also use the Sentry logger (const { logger } =
Sentry) to emit a contextual logger.error including identifiers like surveyId,
promotionIssued, and a short message (e.g., "issuePromotion failed" or
"getSurveyInfo failed") instead of console.error; keep promotionIssued
assignment logic but ensure errors are captured and logged via Sentry for
observability.

In `@src/features/survey/pages/SectionBasedSurvey.tsx`:
- Around line 321-331: Wrap the submission flow that calls getSurveyInfo and
issuePromotion in a Sentry span using Sentry.startSpan({ op: "ui.click",
description: "submit survey" }) and ensure the span is finished after the flow
(success or failure); inside the try block keep the existing calls
(getSurveyInfo(surveyId), issuePromotion({ surveyId }),
queryClient.invalidateQueries(...)) and in the catch block call
Sentry.captureException(error) before performing the existing fallback
navigation, so exceptions are recorded and the span is closed on both paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0b444e19-7e77-4454-8420-b87e64261c97

📥 Commits

Reviewing files that changed from the base of the PR and between dbea7a1 and a506eb6.

📒 Files selected for processing (13)
  • src/features/survey-list/components/CustomSurveyList.tsx
  • src/features/survey-list/components/SurveyList.tsx
  • src/features/survey-list/components/UrgentSurveyList.tsx
  • src/features/survey-list/hooks/useProcessedOngoingSurveys.ts
  • src/features/survey-list/pages/SurveyList.tsx
  • src/features/survey-list/service/surveyList/types.ts
  • src/features/survey/components/SurveyRewardInfoCard.tsx
  • src/features/survey/hooks/useSurveyNavigation.ts
  • src/features/survey/pages/Complete.tsx
  • src/features/survey/pages/SectionBasedSurvey.tsx
  • src/features/survey/pages/Survey.tsx
  • src/features/survey/service/surveyParticipation/types.ts
  • src/shared/types/surveyList.ts

Copy link

@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.

🧹 Nitpick comments (1)
src/features/survey-list/components/SurveyList.tsx (1)

1-1: 기본 리워드 fallback도 같은 공용 규칙으로 묶어두는 편이 안전합니다.

시간 계산은 공용 헬퍼를 쓰는데 금액은 여기서만 200으로 다시 하드코딩하고 있습니다. 기본 리워드가 다시 바뀌면 규칙이 쉽게 어긋날 수 있으니, fallback 금액까지 공용 유틸/상수로 함께 재사용하면 유지보수가 더 쉬워집니다.

Also applies to: 81-81

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

In `@src/features/survey-list/components/SurveyList.tsx` at line 1, The fallback
reward amount is hardcoded as 200 in SurveyList.tsx while time calculations
reuse getEstimatedTimeByPrice; replace the local hardcoded fallback with a
shared constant or util (e.g., export a DEFAULT_REWARD or getDefaultReward()
from the same module that provides getEstimatedTimeByPrice) and import that
constant into SurveyList.tsx so both the fallback amount and any other reward
logic reference the single shared symbol instead of duplicating the value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/features/survey-list/components/SurveyList.tsx`:
- Line 1: The fallback reward amount is hardcoded as 200 in SurveyList.tsx while
time calculations reuse getEstimatedTimeByPrice; replace the local hardcoded
fallback with a shared constant or util (e.g., export a DEFAULT_REWARD or
getDefaultReward() from the same module that provides getEstimatedTimeByPrice)
and import that constant into SurveyList.tsx so both the fallback amount and any
other reward logic reference the single shared symbol instead of duplicating the
value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d36a3467-89d8-4d02-a8db-0bcca19ecab7

📥 Commits

Reviewing files that changed from the base of the PR and between a506eb6 and 9075537.

📒 Files selected for processing (3)
  • src/features/survey-list/components/SurveyList.tsx
  • src/features/survey/components/SurveyRewardInfoCard.tsx
  • src/features/survey/hooks/useSurveyNavigation.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/features/survey/components/SurveyRewardInfoCard.tsx
  • src/features/survey/hooks/useSurveyNavigation.ts

@chldsbdud chldsbdud merged commit 0d683da into main Mar 12, 2026
5 checks passed
@chldsbdud chldsbdud deleted the OMF-258 branch March 12, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feat 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant