Skip to content

[OMF-332] 인트로 설문 유입·open-stats 연동 및 섹션 설문 UX/분기 판정 개선#162

Merged
chldsbdud merged 9 commits into
mainfrom
OMF-332
May 12, 2026
Merged

[OMF-332] 인트로 설문 유입·open-stats 연동 및 섹션 설문 UX/분기 판정 개선#162
chldsbdud merged 9 commits into
mainfrom
OMF-332

Conversation

@chldsbdud

@chldsbdud chldsbdud commented May 10, 2026

Copy link
Copy Markdown
Collaborator

📌 Feat: 인트로 설문 유입·open-stats 연동 및 섹션 설문 UX/분기 판정 개선

요약

인트로에서 비인증 사용자를 대상으로 공개 설문 통계(open-stats)를 노출하고, 로그인 이후 자연스럽게 설문 참여로 이어지는 플로우를 연결했습니다.
또한 섹션 설문 UI를 다듬고, 마지막 섹션 여부를 잘못 판정하던 분기 로직을 수정해 치어 메시지 및 제출 단계 판정 오류를 개선했습니다.


주요 변경 사항

인트로 · 공개 통계 API 연동

  • GET /v1/surveys/open-stats 연동

    • openSurveyCount
    • maxRewardCoin
  • useSurveyOpenStats 훅 추가

  • 설문 배너 노출 조건 및 카피 개선

    • 오픈 설문 수
    • 최대 리워드 코인 기준 노출
  • 참여하기 액션 플로우 연결

    • 로그인 이후 ongoing/all 목록 조회
    • 참여 가능한(isEligible) 설문만 필터링
    • 최고 리워드 설문 우선 선택
    • 스크리닝 존재 시 oxScreening
    • 없을 경우 설문 info로 진입
  • 참여 불가 또는 목록 조회 실패 시

    • 바텀시트 안내 노출
    • 확인 시 홈 이동 처리

설문 정보 API 타입 정리

  • SurveyBasicInfototalSections 반영

  • 섹션 진행/네비게이션 state에서는 기존 sectionCount 키 유지

    • 단, 값은 totalSections 기준으로 사용하도록 정리

섹션 설문 UI 개선

  • 프로그레스 바 하단 안내 영역 스타일 개선

    • 마일스톤 메시지
    • 마지막 구간 메시지
  • 적용 스타일

    • background
    • border-radius
    • padding

🐞 버그 수정

마지막 섹션 오인 판정 수정

문제

기존에는 아래 조건만으로 마지막 섹션 여부를 판단했습니다.

actualNextSection === 0

하지만 분기형 객관식 문항에서 아직 답변하지 않은 상태에서도 API가 nextSection: 0을 반환하는 경우가 있어, 실제 마지막 섹션이 아닌데도 종료 직전으로 오인되는 문제가 있었습니다.

영향

  • 아직 마지막이 아닌데도

    • 거의 다 왔어요 메시지가 잠깐 노출됨
  • 이후 선택값에 따라 분기가 결정되면

    • 잘 하고 있어요 메시지와 혼재되어 보이는 현상 발생
  • 동일 로직이 제출 단계(isBeforeSubmitStep) 판단에도 영향을 줄 가능성 존재

대응

현재 섹션이 아래 조건을 만족하는 경우:

  • isSectionDecidable === true
  • 분기형 multipleChoice 문항 존재
  • 아직 답변 없음

awaitingDecidableBranch 상태로 간주하도록 수정했습니다.

이 상태에서는 isLastSectiontrue로 판단하지 않도록 변경해,
분기 확정 이전에 마지막 섹션으로 오인되지 않도록 처리했습니다.


🔗 관련 이슈


✅ 리뷰 요청 사항

  • 🙂 크게 우려되는 사항은 없어요. 가볍게 리뷰 부탁드립니다.
  • ⚠️ 사이드 이펙트 가능성이 있어요. 자세히 봐주세요.
  • 🤔 분기 판정 로직 변경이 적절한지 함께 확인 부탁드립니다.

🎨 스크린샷

페이지 1 페이지 2

페이지 3 페이지 4

Summary by CodeRabbit

  • 새로운 기능

    • 로그인 사용자를 위한 "참여하기" 흐름 추가 — 가장 높은 보상 설문 자동 선택 및 참여로 이동
    • 다중 섹션 설문용 진행도 표시줄 및 섹션 기반 마일스톤/최종 단계 메시지 추가
    • 열린 설문 수·최대 보상 코인 정보가 있는 프로모션 배너 추가
  • 개선 사항

    • 부적격 안내창에 제목/설명 커스터마이징 및 확인 동작 개선
    • 참여 중 에러/빈 결과 시 홈으로 안전하게 복귀하도록 처리 개선

Review Change Stack

@chldsbdud chldsbdud self-assigned this May 10, 2026
@chldsbdud chldsbdud added ✨Feat 새로운 기능 추가 🐛 Fix 버그 수정 labels May 10, 2026
@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown

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

공개 설문 통계 API와 훅 추가, 진행중 설문에서 자격 있는 최고 보상 설문 자동 선택/참여 흐름 구현, 섹션 기반 설문에 진행률·마일스톤 UI 추가, 부적격 바텀시트에 커스터마이징 옵션을 도입합니다.

변경사항

설문 참여 및 진행률 추적 흐름

Layer / File(s) Summary
데이터 계약
src/features/survey-list/service/surveyList/types.ts, src/features/survey/service/surveyParticipation/types.ts, src/features/survey/hooks/useSectionBasedSurveyController.ts, src/features/screening/components/IneligibleSurveyBottomSheet.tsx
SurveyOpenStats 인터페이스(openSurveyCount, maxRewardCoin), SurveyBasicInfo.totalSections?, SectionBasedSurveyState.sectionCount?, IneligibleSurveyBottomSheetPropstitle?, description?, confirmNavigatesHome? 추가.
API 및 쿼리 레이어
src/features/survey-list/service/surveyList/api.ts, src/features/survey-list/hooks/useAllOngoingSurveys.ts, src/features/survey-list/hooks/useSurveyOpenStats.ts
getSurveyOpenStats() API 추가 및 useSurveyOpenStats 훅 도입. useAllOngoingSurveysoptions?: { enabled?: boolean } 파라미터로 쿼리 실행 제어 추가.
데이터 매핑 및 섹션 컨트롤러
src/features/survey-list/hooks/useProcessedOngoingSurveys.ts, src/features/survey/hooks/useSectionBasedSurveyController.ts
mapOngoingSurveySummaryToSurveyListItem 추출·내보내기. 섹션 컨트롤러에 sectionCount 페칭(없을 시 API 조회), progress 계산(클램프), 마지막 섹션 판정에 선택지 빈 응답 검사 추가, isBeforeSubmitStep 반환.
UI 컴포넌트 및 설정
src/features/screening/components/IneligibleSurveyBottomSheet.tsx, src/features/survey/pages/SectionBasedSurvey.tsx
IneligibleSurveyBottomSheet에 제목/설명·confirm 동작 제어 추가. SectionBasedSurvey에 ProgressBar와 최종/마일스톤 메시지 렌더링 추가(컨트롤러 상태 사용).
통합 및 네비게이션
src/app/Intro.tsx, src/features/survey/pages/Survey.tsx
Intro에 참여 버튼 핸들러(토스 로그인 → ongoing 설문 조회(최대 100) → 자격 필터링 → 최고 가격 설문 선택 → allOngoingSurveys 쿼리 무효화 → 스크리닝 확인 → 온보딩/oxScreening/설문으로 라우팅) 및 프로모션 배너 노출(useSurveyOpenStats) 추가. Survey 페이지에서 sectionCount를 네비게이션 상태로 전달.

예상 코드 리뷰 노력

🎯 4 (Complex) | ⏱️ ~45 minutes

관련된 가능성 있는 PR

추천 리뷰어

  • 78-artilleryman
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 제목은 이 PR의 주요 변경 사항들을 정확하게 반영하고 있습니다. 인트로 페이지의 설문 유입 흐름, open-stats 연동, 섹션 설문 UX/분기 판정 개선이라는 세 가지 핵심 변경 사항을 간결하게 요약했습니다.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch OMF-332

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@chldsbdud chldsbdud changed the title Omf 332 [OMF-332] 인트로 설문 유입·open-stats 연동 및 섹션 설문 UX/분기 판정 개선 May 10, 2026

@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/features/survey/pages/Survey.tsx (1)

230-270: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

handleStart 버튼 클릭 핸들러에 Sentry ui.click span 계측을 추가하세요.

라인 230의 핵심 CTA 핸들러가 라우팅까지 수행하지만, 사용자 인터랙션 span이 없어 모니터링 공백이 발생합니다. 코딩 가이드라인에 따라 Sentry.startSpan(op: 'ui.click')span.setAttribute()를 통해 설문 ID와 출처를 추적하고, Sentry import를 추가하세요.

🔧 제안 diff
+import * as Sentry from "@sentry/react";
...
 const handleStart = () => {
-  if (isStartDisabled) {
-    if (surveyBasicInfoData?.isScreenRequired) {
-      navigate(`/oxScreening?surveyId=${numericSurveyId}`, { replace: true });
-    } else if (surveyBasicInfoData?.isScreened) {
-      setErrorDialog({
-        open: true,
-        title: "스크리닝 조건이 맞지 않습니다",
-        description: "설정하신 스크리닝 조건에 맞지 않아 설문에 참여할 수 없어요.",
-        redirectTo: "/home",
-      });
-    }
-    return;
-  }
-
-  if (surveyBasicInfoData?.isSurveyResponded) {
-    openToast("이미 참여한 설문입니다.", {
-      type: "bottom",
-      lottie: "https://static.toss.im/lotties-common/error-yellow-spot.json",
-      higherThanCTA: true,
-    });
-    return;
-  }
-
-  const source = locationState?.source ?? "main";
-
-  navigate("/survey/section", {
-    state: {
-      surveyId: numericSurveyId,
-      currentSection: 1,
-      sectionCount: surveyBasicInfoData?.totalSections,
-      answers: {},
-      previousAnswers: {},
-      surveyTitle: surveyTitle ?? "",
-      surveyDescription: surveyDescription ?? "",
-      source,
-      price: surveyBasicInfoData?.price ?? priceFromState,
-    },
-  });
+  Sentry.startSpan(
+    { name: "Survey start click", op: "ui.click" },
+    (span) => {
+      span.setAttribute("survey.id", numericSurveyId ?? -1);
+      span.setAttribute("survey.source", locationState?.source ?? "main");
+
+      if (isStartDisabled) {
+        if (surveyBasicInfoData?.isScreenRequired) {
+          navigate(`/oxScreening?surveyId=${numericSurveyId}`, { replace: true });
+        } else if (surveyBasicInfoData?.isScreened) {
+          setErrorDialog({
+            open: true,
+            title: "스크리닝 조건이 맞지 않습니다",
+            description: "설정하신 스크리닝 조건에 맞지 않아 설문에 참여할 수 없어요.",
+            redirectTo: "/home",
+          });
+        }
+        return;
+      }
+
+      if (surveyBasicInfoData?.isSurveyResponded) {
+        openToast("이미 참여한 설문입니다.", {
+          type: "bottom",
+          lottie: "https://static.toss.im/lotties-common/error-yellow-spot.json",
+          higherThanCTA: true,
+        });
+        return;
+      }
+
+      const source = locationState?.source ?? "main";
+      navigate("/survey/section", {
+        state: {
+          surveyId: numericSurveyId,
+          currentSection: 1,
+          sectionCount: surveyBasicInfoData?.totalSections,
+          answers: {},
+          previousAnswers: {},
+          surveyTitle: surveyTitle ?? "",
+          surveyDescription: surveyDescription ?? "",
+          source,
+          price: surveyBasicInfoData?.price ?? priceFromState,
+        },
+      });
+    },
+  );
 };
🤖 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/features/survey/pages/Survey.tsx` around lines 230 - 270, Wrap the
handleStart logic in a Sentry UI click span: import Sentry, call
Sentry.startSpan({ op: "ui.click" }) at the start of handleStart, set attributes
on the span (e.g., span.setAttribute("survey.id", numericSurveyId) and
span.setAttribute("survey.source", source or locationState?.source ?? "main")),
and finish the span right before each early return and after the navigate call
so the span always ends; ensure span is cleaned up in every control path
(errors/returns) and do not change existing navigation/state logic in
handleStart.
🧹 Nitpick comments (4)
src/features/survey-list/hooks/useProcessedOngoingSurveys.ts (1)

86-86: ⚡ Quick win

매직 넘버를 상수로 추출하세요.

기본 가격 200이 두 곳(lines 86, 89)에서 하드코딩되어 있습니다. 이를 파일 상단에 DEFAULT_SURVEY_PRICE 같은 명명된 상수로 추출하면 유지보수성과 의도 전달이 개선됩니다.

♻️ 제안된 수정

파일 상단에 상수 추가:

 const DEFAULT_TOPIC: SurveyListItem["topicId"] = "DAILY_LIFE";
+const DEFAULT_SURVEY_PRICE = 200;

lines 86, 89에서 사용:

-		surveyIdToPrice.set(s.surveyId, s.price ?? 200);
+		surveyIdToPrice.set(s.surveyId, s.price ?? DEFAULT_SURVEY_PRICE);
-		(sum, id) => sum + (surveyIdToPrice.get(id) ?? 200),
+		(sum, id) => sum + (surveyIdToPrice.get(id) ?? DEFAULT_SURVEY_PRICE),

Also applies to: 89-89

🤖 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/features/survey-list/hooks/useProcessedOngoingSurveys.ts` at line 86,
Extract the hardcoded default price (200) into a named constant at the top of
the file (e.g., DEFAULT_SURVEY_PRICE) and replace both occurrences where the
fallback is used (the nullish fallback in surveyIdToPrice.set(s.surveyId,
s.price ?? 200) and the other similar occurrence) to use that constant; update
any related JSDoc or comments if present and ensure imports/exports are
unchanged.
src/features/survey-list/hooks/useSurveyOpenStats.ts (1)

4-9: 💤 Low value

staleTime 설정 검토 권장.

open-stats는 비인증 공개 통계로 자주 바뀌지 않으므로 staleTime을 부여하면 인트로 재진입 시 불필요한 재요청을 줄일 수 있습니다.

♻️ 제안 변경
 export const useSurveyOpenStats = () => {
 	return useQuery({
 		queryKey: ["surveyOpenStats"],
 		queryFn: getSurveyOpenStats,
+		staleTime: 60 * 1000,
 	});
 };
🤖 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/features/survey-list/hooks/useSurveyOpenStats.ts` around lines 4 - 9, The
query useSurveyOpenStats currently calls useQuery with queryKey
["surveyOpenStats"] and queryFn getSurveyOpenStats but no staleTime; since
open-stats are unauthenticated and infrequently changing, add a staleTime (e.g.,
5–15 minutes) to the useQuery options to prevent unnecessary refetches on
re-entry—update useSurveyOpenStats to pass staleTime: 300000 (or chosen ms)
alongside queryKey and queryFn.
src/app/Intro.tsx (2)

21-31: 💤 Low value

pickHighestPriceAmongEligible 단일 패스로 단순화 가능.

현재는 filter → max → filter → sort 과정을 거쳐 동일 데이터를 두 번 순회합니다. 한 번의 reduce로도 동일한 동작(최고가 중 최저 surveyId 우선)이 가능합니다. 동작상 동등하며 가독성·미세한 성능 모두 약간 개선됩니다.

♻️ 제안 변경
 function pickHighestPriceAmongEligible(
 	list: OngoingSurveySummary[],
 ): OngoingSurveySummary | undefined {
-	const eligible = list.filter((s) => s.isEligible ?? false);
-	if (eligible.length === 0) return undefined;
-	const maxPrice = Math.max(...eligible.map((s) => s.price ?? 0));
-	const tied = eligible.filter((s) => (s.price ?? 0) === maxPrice);
-	tied.sort((a, b) => a.surveyId - b.surveyId);
-	return tied[0];
+	return list
+		.filter((s) => s.isEligible ?? false)
+		.reduce<OngoingSurveySummary | undefined>((best, cur) => {
+			if (!best) return cur;
+			const bp = best.price ?? 0;
+			const cp = cur.price ?? 0;
+			if (cp > bp) return cur;
+			if (cp === bp && cur.surveyId < best.surveyId) return cur;
+			return best;
+		}, undefined);
 }
🤖 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/app/Intro.tsx` around lines 21 - 31, The function
pickHighestPriceAmongEligible currently filters and scans the list multiple
times; replace that with a single-pass reduce over list to track the best
candidate: skip non-eligible items (s.isEligible), compare (s.price ?? 0) to
current maxPrice and update the best item when price is higher or when equal but
s.surveyId is smaller; return the reduced best item (or undefined) so you
eliminate the separate eligible, maxPrice, tied, and sort steps while preserving
the "highest price, tie-break by smallest surveyId" behavior.

145-241: ⚖️ Poor tradeoff

Sentry 로깅 및 스팬 추적 적용을 권장합니다.

handleParticipateLogin 함수의 try-catch 블록에서 console.error만 사용되고 있습니다. 코딩 가이드라인에 따라 예외는 Sentry.captureException(error)로 캡처하고, UI 클릭은 Sentry.startSpan({ op: 'ui.click', ... })으로, API 호출은 op: 'http.client' 스팬으로 감싸면 로그인 플로우의 오류 및 이탈 경로 가시성을 크게 향상할 수 있습니다. 프로젝트에 이미 Sentry가 초기화되어 있고 다른 모듈에서도 활용 중이므로, 신규 코드부터 점진적으로 적용하기 좋은 시점입니다.

♻️ 적용 예시
-import { useEffect, useState } from "react";
+import * as Sentry from "@sentry/react";
+import { useEffect, useState } from "react";
@@
-	const handleParticipateLogin = async () => {
-		pushGtmEvent({ ... });
-		try {
-			const { authorizationCode, referrer } = await appLogin();
-			const loginApiResponse = await loginApi(authorizationCode, referrer);
+	const handleParticipateLogin = async () => {
+		await Sentry.startSpan(
+			{ op: "ui.click", name: "intro.participate" },
+			async (span) => {
+				span.setAttribute("openSurveyCount", openSurveyCount);
+				pushGtmEvent({ ... });
+				try {
+					const { authorizationCode, referrer } = await appLogin();
+					const loginApiResponse = await Sentry.startSpan(
+						{ op: "http.client", name: "POST /v1/auth/login" },
+						() => loginApi(authorizationCode, referrer),
+					);
@@
-			} catch (error) {
-				console.error("진행 설문 목록 조회 실패:", error);
+			} catch (error) {
+				Sentry.captureException(error);
				setIneligibleSurveySheet({ ... });
				return;
			}
@@
-		} catch (error) {
-			console.error("토스 로그인 실패:", error);
-		}
+				} catch (error) {
+					Sentry.captureException(error);
+				}
+			},
+		);
	};
🤖 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/app/Intro.tsx` around lines 145 - 241, Wrap the login flow in Sentry
spans and replace console.error calls with Sentry.captureException: start a
ui.click span at the start of handleParticipateLogin (op: 'ui.click') and create
nested http.client spans around async calls appLogin(), loginApi(),
getAllOngoingSurveys(), and getScreenings() so each API call is traced; in each
catch block (the outer try-catch in handleParticipateLogin and the inner catches
around getAllOngoingSurveys and getScreenings) call
Sentry.captureException(error) (optionally setting span.setStatus or adding
tags/context like { step: 'getAllOngoingSurveys' }) and finish/close any open
spans before returning/continuing; ensure spans are finished in finally blocks
where needed so Sentry shows complete timing for handleParticipateLogin,
loginApi, getAllOngoingSurveys, and getScreenings.
🤖 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/Intro.tsx`:
- Around line 199-230: The current try/catch around getScreenings leaves
hasScreening=false on errors which can wrongly route users into the survey;
change the error handling to either perform one retry of getScreenings or set a
explicit screeningCheckFailed flag and branch to show the
IneligibleSurveyBottomSheet instead of navigating into the survey when the check
fails. Concretely: in the block that calls getScreenings (function getScreenings
and local var hasScreening), on catch attempt one immediate retry of
getScreenings and only if that also fails set screeningCheckFailed=true; then,
in the onboardingCompleted branch where navigate and navigateToSurveyInfo are
used, guard those navigations with screeningCheckFailed (or hasScreening) so
that if screeningCheckFailed is true you open the IneligibleSurveyBottomSheet
(or another blocking UI) instead of calling navigate or navigateToSurveyInfo.
- Around line 184-191: When targetSummary is falsy in Intro.tsx (the branch
using setIneligibleSurveySheet), avoid unguarded navigation to /home: either (A)
pass confirmNavigatesHome={false} into the IneligibleSurveyBottomSheet and
handle navigation in its onClose by checking
loginApiResponse.onboardingCompleted and routing to /onboarding when false, or
(B) before showing the sheet (the !targetSummary branch that calls
setIneligibleSurveySheet) check loginApiResponse.onboardingCompleted and
navigate to /onboarding immediately for incomplete users instead of allowing the
default confirmNavigatesHome behavior; update the code paths that call
pickHighestPriceAmongEligible and setIneligibleSurveySheet accordingly so
onboarding checks occur whenever the empty/error sheet would otherwise navigate
home.

In `@src/features/survey/hooks/useSectionBasedSurveyController.ts`:
- Around line 85-95: 현재 decidableBranchQuestion가 questions.find로 첫 번째
isSectionDecidable 객관식 문항만 검사해 뒤쪽 decider 문항의 미응답을 놓칠 수 있으니, 단일 항목 찾기 대신 해당 섹션의
모든 decider 객관식 문항을 검사하도록 변경하세요: questions에서 q.isSectionDecidable && q.type ===
"multipleChoice"인 항목들에 대해 answersRef.current[q.questionId]가 비어있는 항목이 하나라도
있는지(some)를 사용하여 awaitingDecidableBranch를 계산하고, 이 값을 isLastSection 계산(사용되는
actualNextSection과 결합) 부분에서 사용하도록 고치세요; 관련 식별자: decidableBranchQuestion,
awaitingDecidableBranch, isMultipleChoiceAnswerEmpty, answersRef.current,
isLastSection, actualNextSection.
- Around line 138-151: The getSurveyInfo call inside the useEffect (in
useSectionBasedSurveyController) currently swallows errors; wrap the async fetch
with a Sentry span and proper error reporting: start a span via
Sentry.startSpan({ op: 'http.client', description: 'getSurveyInfo' }) before
calling getSurveyInfo, finish the span after the request, and in the catch block
call Sentry.captureException(error) and logger.warn with the error and context;
ensure setSurveySectionCount unchanged on success and always finish the span in
finally to avoid leaked spans.

---

Outside diff comments:
In `@src/features/survey/pages/Survey.tsx`:
- Around line 230-270: Wrap the handleStart logic in a Sentry UI click span:
import Sentry, call Sentry.startSpan({ op: "ui.click" }) at the start of
handleStart, set attributes on the span (e.g., span.setAttribute("survey.id",
numericSurveyId) and span.setAttribute("survey.source", source or
locationState?.source ?? "main")), and finish the span right before each early
return and after the navigate call so the span always ends; ensure span is
cleaned up in every control path (errors/returns) and do not change existing
navigation/state logic in handleStart.

---

Nitpick comments:
In `@src/app/Intro.tsx`:
- Around line 21-31: The function pickHighestPriceAmongEligible currently
filters and scans the list multiple times; replace that with a single-pass
reduce over list to track the best candidate: skip non-eligible items
(s.isEligible), compare (s.price ?? 0) to current maxPrice and update the best
item when price is higher or when equal but s.surveyId is smaller; return the
reduced best item (or undefined) so you eliminate the separate eligible,
maxPrice, tied, and sort steps while preserving the "highest price, tie-break by
smallest surveyId" behavior.
- Around line 145-241: Wrap the login flow in Sentry spans and replace
console.error calls with Sentry.captureException: start a ui.click span at the
start of handleParticipateLogin (op: 'ui.click') and create nested http.client
spans around async calls appLogin(), loginApi(), getAllOngoingSurveys(), and
getScreenings() so each API call is traced; in each catch block (the outer
try-catch in handleParticipateLogin and the inner catches around
getAllOngoingSurveys and getScreenings) call Sentry.captureException(error)
(optionally setting span.setStatus or adding tags/context like { step:
'getAllOngoingSurveys' }) and finish/close any open spans before
returning/continuing; ensure spans are finished in finally blocks where needed
so Sentry shows complete timing for handleParticipateLogin, loginApi,
getAllOngoingSurveys, and getScreenings.

In `@src/features/survey-list/hooks/useProcessedOngoingSurveys.ts`:
- Line 86: Extract the hardcoded default price (200) into a named constant at
the top of the file (e.g., DEFAULT_SURVEY_PRICE) and replace both occurrences
where the fallback is used (the nullish fallback in
surveyIdToPrice.set(s.surveyId, s.price ?? 200) and the other similar
occurrence) to use that constant; update any related JSDoc or comments if
present and ensure imports/exports are unchanged.

In `@src/features/survey-list/hooks/useSurveyOpenStats.ts`:
- Around line 4-9: The query useSurveyOpenStats currently calls useQuery with
queryKey ["surveyOpenStats"] and queryFn getSurveyOpenStats but no staleTime;
since open-stats are unauthenticated and infrequently changing, add a staleTime
(e.g., 5–15 minutes) to the useQuery options to prevent unnecessary refetches on
re-entry—update useSurveyOpenStats to pass staleTime: 300000 (or chosen ms)
alongside queryKey and queryFn.
🪄 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

Run ID: 74ae219e-baad-409b-8418-2cdb6fe7a61a

📥 Commits

Reviewing files that changed from the base of the PR and between 8476a98 and c307c25.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • src/app/Intro.tsx
  • src/features/screening/components/IneligibleSurveyBottomSheet.tsx
  • src/features/survey-list/hooks/useAllOngoingSurveys.ts
  • src/features/survey-list/hooks/useProcessedOngoingSurveys.ts
  • src/features/survey-list/hooks/useSurveyOpenStats.ts
  • src/features/survey-list/service/surveyList/api.ts
  • src/features/survey-list/service/surveyList/types.ts
  • src/features/survey/hooks/useSectionBasedSurveyController.ts
  • src/features/survey/pages/SectionBasedSurvey.tsx
  • src/features/survey/pages/Survey.tsx
  • src/features/survey/service/surveyParticipation/types.ts

Comment thread src/app/Intro.tsx
Comment thread src/app/Intro.tsx
Comment thread src/features/survey/hooks/useSectionBasedSurveyController.ts Outdated
Comment thread src/features/survey/hooks/useSectionBasedSurveyController.ts

@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/app/Intro.tsx`:
- Around line 139-233: Import Sentry at the top (import * as Sentry from
'@sentry/react') and instrument the participate-login flow in
handleParticipateLogin: for the getAllOngoingSurveys and getScreenings try/catch
blocks call Sentry.captureException(error) in their catch blocks and
create/finish a span with op: 'http.client' around the failing API call
(referencing getAllOngoingSurveys and getScreenings); in the outermost catch for
the login flow (the catch that logs "토스 로그인 실패:") also call
Sentry.captureException(error). Additionally, wrap the UI button click that
invokes handleParticipateLogin with a Sentry.startSpan({ op: 'ui.click',
description: 'participate login click' }) and finish that span when the handler
completes/returns (reference the component/button that calls
handleParticipateLogin).
🪄 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

Run ID: 927b74f8-196c-487b-8291-ecdd28eb8eb7

📥 Commits

Reviewing files that changed from the base of the PR and between c307c25 and 8f7e871.

📒 Files selected for processing (3)
  • src/app/Intro.tsx
  • src/features/survey/hooks/useSectionBasedSurveyController.ts
  • src/features/survey/pages/SectionBasedSurvey.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/features/survey/hooks/useSectionBasedSurveyController.ts
  • src/features/survey/pages/SectionBasedSurvey.tsx

Comment thread src/app/Intro.tsx
@chldsbdud chldsbdud merged commit 2259fdb into main May 12, 2026
5 checks passed
@chldsbdud chldsbdud deleted the OMF-332 branch May 12, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feat 새로운 기능 추가 🐛 Fix 버그 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant