Skip to content

[feat] 주요활동 선택 스텝 리뉴얼#513

Open
jstar000 wants to merge 20 commits intodevelopfrom
feat/funnel-activity-renewal/#512
Open

[feat] 주요활동 선택 스텝 리뉴얼#513
jstar000 wants to merge 20 commits intodevelopfrom
feat/funnel-activity-renewal/#512

Conversation

@jstar000
Copy link
Copy Markdown
Contributor

@jstar000 jstar000 commented Apr 9, 2026

📌 Summary

관련 있는 Issue를 태그해주세요. (e.g. > - #1)

해당 PR에 대한 작업 내용을 요약하여 작성해주세요.

📄 Tasks

해당 PR에 수행한 작업을 작성해주세요.

🔍 To Reviewer

  • 주요활동 선택 - 주요활동/가구 API 응답은 MSW로 모킹한 응답입니다

리뷰어에게 요청하는 내용을 작성해주세요.

📸 Screenshot

2026-04-09.6.45.05.mov

작업한 내용에 대한 스크린샷을 첨부해주세요.

jstar000 added 20 commits April 8, 2026 16:40
- B-2 디자인 수정을 반영해 InteriorStyle 스텝에 MoodboardCard 추가, 기존 CardImage 제거
- MoodboardCard는 공컴이 아니므로 InteriorStyle 폴더 안에 선언
- 164-240 반응형 적용
- div -> button 변경으로 키보드 포커스 등 접근성 제공
- 'children', 'type'을 omit (type은 button으로 고정)
-
… 명세 변경 반영)

- useActivityOptionsQuery → useActivitiesQuery + useFurnitureCategoriesQuery 분리
- API 엔드포인트 추가, queryKey 추가,
- MSW 핸들러, prefetchStaticData에 주요활동/가구 API 변경사항 적용
- 새 API에서 필수가구까지 넘겨주므로 MAIN_ACTIVITY_VALIDATION(필수가구 하드코딩 매핑) 삭제
- 필수가구 매핑 관련 코드(onActivityChange, selectedValues) 삭제
- 새 API에서 필수가구까지 넘겨주므로 MAIN_ACTIVITY_VALIDATION(필수가구 하드코딩 매핑) 삭제
- 필수가구 매핑 관련 코드(onActivityChange, selectedValues) 삭제
- ActivityIcon.ts: 주요 활동 종류에 따라 적절한 아이콘 매핑
- ChipColor가 weak인 경우 추가(기존에는 strong인 경우뿐)
@jstar000 jstar000 self-assigned this Apr 9, 2026
@jstar000 jstar000 requested a review from a team as a code owner April 9, 2026 09:48
@jstar000 jstar000 added 💫 Feature 기능 개발 🤙 지성 웹 36기 임지성 🛠️ Refactor 리팩토링 labels Apr 9, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 활동 형태 선택 인터페이스를 모달 시트 방식으로 개선하여 더 직관적인 선택 경험 제공
    • 활동별 필수 가구 정보 명확화로 선택 안내 강화
  • UI/스타일 개선

    • 분위기 보드 카드 컴포넌트 리뉴얼로 더 나은 시각적 피드백 제공
    • 아이콘 및 컴포넌트 라이브러리 확대로 일관된 디자인 적용
    • 레이아웃 및 간격 최적화로 화면 배치 개선
  • 성능 최적화

    • 백그라운드 데이터 캐싱 전략 개선

Walkthrough

이 PR은 이미지 설정 단계의 "Activity Info" 화면을 v1에서 v2 API 아키텍처로 마이그레이션합니다. 새로운 활동 및 가구 카테고리 엔드포인트를 도입하고, 관련 훅과 컴포넌트를 리팩토링하며, 레거시 카드 컴포넌트를 제거하고 새로운 UI 컴포넌트를 추가합니다.

Changes

Cohort / File(s) Summary
MSW 및 API 엔드포인트
src/main.tsx, src/mocks/handlers.ts, src/shared/constants/apiEndpoints.ts
개발 환경에서만 MSW를 시작하는 async enableMocking() 함수 추가. v1 대시보드 정보 엔드포인트를 v2 활동(/api/v2/dashboard/activities)과 가구 카테고리(/api/v2/dashboard/categories) 엔드포인트로 교체.
API 쿼리 훅
src/pages/imageSetup/apis/queries/useActivitiesQuery.ts, src/pages/imageSetup/apis/queries/useFurnitureCategoriesQuery.ts, src/pages/imageSetup/apis/queries/useActivityOptionsQuery.ts
새로운 useActivitiesQueryuseFurnitureCategoriesQuery 훅 추가. 레거시 useActivityOptionsQuery 제거.
Activity Info 훅 리팩토링
src/pages/imageSetup/hooks/activityInfo/useActivityInfo.ts, src/pages/imageSetup/hooks/activityInfo/useActivitySelection.ts, src/pages/imageSetup/hooks/activityInfo/useCategorySelection.ts
activityOptionsData 파라미터 제거 및 두 개의 독립적인 쿼리 훅으로 교체. 폼 상태를 activityType/selectiveIds에서 activity/furnitureIds로 변경. 카테고리 선택 모드 기반 제어 흐름 추가.
새로운 Activity Info UI 컴포넌트
src/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.tsx, src/pages/imageSetup/steps/activityInfo/SelectTrigger.tsx, src/pages/imageSetup/steps/activityInfo/activityIcons.ts
활동 유형 선택 시트, 트리거 버튼, 아이콘 매핑 유틸리티 추가.
Activity Info UI 스타일링
src/pages/imageSetup/steps/activityInfo/ActivityInfo.css.ts, src/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.css.ts, src/pages/imageSetup/steps/activityInfo/SelectTrigger.css.ts
새로운 컴포넌트와 개선된 레이아웃을 위한 Vanilla Extract 스타일 추가. 클래스명 및 구조 개선.
Activity Info 메인 컴포넌트
src/pages/imageSetup/steps/activityInfo/ActivityInfo.tsx
useActivityOptionsQuery 제거. ActivityTypeSheet 오버레이를 통한 활동 선택 UI 재구성. 가구 선택을 Chip 컴포넌트로 변경.
Moodboard 컴포넌트
src/pages/imageSetup/steps/interiorStyle/MoodBoard.tsx, src/pages/imageSetup/steps/interiorStyle/MoodboardCard.tsx, src/pages/imageSetup/steps/interiorStyle/MoodboardCard.css.ts
레거시 CardImage를 새로운 MoodboardCard 컴포넌트로 교체. 선택 상태 관리 및 스타일링 개선.
Interior Style 컴포넌트
src/pages/imageSetup/steps/interiorStyle/InteriorStyle.tsx, src/pages/imageSetup/steps/interiorStyle/InteriorStyle.css.ts
FunnelHeader 제거 및 TextHeading으로 교체. 버튼을 CtaButton에서 ActionButton으로 변경. 레이아웃 및 버튼 위치 조정.
타입 정의 업데이트
src/pages/imageSetup/types/apis/activityInfo.ts, src/pages/imageSetup/types/funnel/activityInfo.ts, src/pages/imageSetup/types/funnel/steps.ts
API 응답 타입 재구조화(ActivityItem, ActivitiesResponse, FurnitureCategoriesResponse). 폼 데이터 타입 필드명 변경. CATEGORY_SELECTION_MODE 추가.
Zustand 스토어
src/pages/imageSetup/stores/useFunnelStore.ts
activityInfo 필드를 activityType/selectiveIds에서 activity/furnitureIds로 업데이트.
쿼리 키 및 정적 데이터
src/shared/constants/queryKey.ts, src/pages/imageSetup/utils/staticDataPrefetch.ts
activityOptions 쿼리 키 제거. activitiesfurnitureCategories 쿼리 키 추가.
Funnel 레이아웃
src/pages/imageSetup/components/layout/FunnelLayout.tsx
currentStep prop을 문자열 합집합에서 FunnelStepKey 타입으로 변경. 네비게이션 바 제목을 동적으로 설정하는 매핑 추가.
공유 UI 컴포넌트
src/shared/components/v2/chip/Chip.tsx, src/shared/components/v2/chip/Chip.css.ts, src/shared/components/v2/icon/Icon.tsx, src/shared/components/v2/textHeading/TextHeading.css.ts, src/shared/components/v2/bottomSheet/DragHandleBottomSheet.tsx
Chipcolordisabled 배리언트 추가. 새로운 아이콘(Book, Cup, Desk, Mouse, ChevronDownFill, Lock) 등록. TextHeadingDragHandleBottomSheet 스타일 조정.
제거된 컴포넌트
src/shared/components/card/cardImage/CardImage.tsx, src/shared/components/card/cardImage/CardImage.css.ts, src/shared/components/card/cardImage/SkeletonCardImage.tsx, src/stories/CardImage.stories.tsx, src/stories/SkeletonCardImage.stories.tsx
레거시 CardImageSkeletonCardImage 컴포넌트 및 관련 스타일, Storybook 스토리 제거.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

검토 포인트:

  • API 마이그레이션에 따른 데이터 구조 변경 추적 필요
  • 훅 간 데이터 흐름 및 의존성 검증
  • 새로운 CategorySelectionMode 기반 선택 로직의 정확성 확인
  • 제거된 컴포넌트의 완전한 대체 여부 검증
  • 타입 안정성 및 폼 상태 관리 일관성

Suggested reviewers

  • maehwasoo
  • soyyyyy
  • earl9rey
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 [type] 형식을 따르며 21자로 50자 이내 요구사항을 충족하고, 주요활동 선택 스텝 리뉴얼이라는 변경사항의 핵심을 명확히 반영합니다.
Description check ✅ Passed PR 설명에 이슈 #512 링크, 검토자 주의사항(MSW 모킹), 스크린샷이 포함되어 있으며 변경사항과 관련이 있습니다.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/funnel-activity-renewal/#512

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.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

빌드 결과

빌드 성공 🎊

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

🎨 Storybook 빌드 완료!

📚 Storybook: https://686a831b8e000345a949970a-uqpqtoxwfs.chromatic.com/
🔍 Chromatic: https://www.chromatic.com/build?appId=686a831b8e000345a949970a&number=942

📊 빌드 정보

  • 빌드 상태: success
  • 테스트된 스토리: 122개
  • 변경된 컴포넌트: 122개

🔍 시각적 변경사항: 7개 발견

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af7c65b31d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 59 to 61
onBackClick={
currentStep === 'FloorPlanSelect' ? handleBackClick : undefined
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Wire back navigation for non-floor funnel steps

The new header wiring passes undefined for onBackClick outside FloorPlanSelect, but the v2 TitleNavBar back button only invokes the provided callback and no longer falls back to navigate(-1). This makes the visible back button a no-op on InteriorStyle and ActivityInfo, so users cannot move back through the funnel from the header.

Useful? React with 👍 / 👎.

Comment on lines +133 to +135
: isPersistent
? { height: expanded ? EXPANDED_HEIGHT : collapsedHeight }
: {}; // Dismissible: content 높이(auto) + CSS maxHeight가 상한
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep dismissible sheet height aligned with drag logic

For dismissible sheets, this now opens the panel at content height ({} style) while drag dismissal still uses expandedPxRef.current / 2 as the close threshold in handlePointerUp. When the content is shorter than half the viewport (common on larger screens), pressing the drag handle without meaningful movement leaves dragHeight below that threshold and immediately triggers onDismiss, causing accidental sheet closure.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

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

🤖 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/main.tsx`:
- Around line 53-71: The app currently only calls createRoot(...).render(...)
inside the enableMocking() promise success path, so if enableMocking() rejects
the app never mounts; change the flow to always call createRoot(rootElement,
getSentryReactErrorHandlerOptions()).render(...) regardless of enableMocking()
outcome by moving the render call out of the then-only branch and handling
enableMocking() errors separately (e.g., catch the rejection, log the error,
then proceed to render), referencing enableMocking(), createRoot(...).render,
rootElement, getSentryReactErrorHandlerOptions(), and AppErrorFallback so the
ErrorBoundary/HelmetProvider/QueryClientProvider tree is mounted even when
mocking initialization fails.

In `@src/pages/imageSetup/components/layout/FunnelLayout.tsx`:
- Around line 15-22: The FunnelStepKey type is currently local but should be
exported so other funnel-related components can reuse it; change the declaration
to export the type (export type FunnelStepKey = ...) and keep
NAVBAR_TITLE_BY_STEP as Record<FunnelStepKey, string> to preserve typings, then
update any consumers to import { FunnelStepKey } from this module where needed.

In `@src/pages/imageSetup/hooks/activityInfo/useActivityInfo.ts`:
- Around line 148-168: The useEffect may suffer a stale-closure because
activitySelection.getRequiredFurnitureIds is not listed in the dependency array;
update the effect to include either activitySelection or
activitySelection.getRequiredFurnitureIds in the dependencies (instead of
silencing eslint) so the effect re-runs when the selector changes, or if you
deliberately rely on formData.activity driving recalculation, replace the
eslint-disable with a short comment in useActivityInfo explaining that
activitySelection is derived from formData.activity and therefore intentionally
omitted; touch the effect around useEffect and references to
activitySelection.getRequiredFurnitureIds and savedActivityInfo to make the
intent explicit.
- Around line 96-123: The code currently indexes categoriesData?.categories by
fixed positions when calling useCategorySelection (e.g.,
categoriesData?.categories[0]) which can mis-map if the server changes order or
omits items; replace those fixed-index calls by first implementing a helper like
getCategoryByNameEng(nameEng) that returns categoriesData?.categories.find(c =>
c.nameEng === nameEng) ?? null, then call useCategorySelection with
getCategoryByNameEng('BED'), getCategoryByNameEng('SOFA'),
getCategoryByNameEng('TABLE'), and getCategoryByNameEng('SELECTIVE')
respectively, keeping the same CATEGORY_SELECTION_MODE.*, formData, setFormData
and globalConstraints parameters and preserving null fallback behavior.

In `@src/pages/imageSetup/hooks/activityInfo/useCategorySelection.ts`:
- Around line 40-72: The toggleFurniture function is recreated on each render
and passed to each Chip's onClick; wrap toggleFurniture in React.useCallback to
stabilize its reference (so memoized Chips won't re-render unnecessarily).
Create useCallback around the existing toggleFurniture logic and include correct
dependencies: setFormData, formData.furnitureIds (or formData), mode,
globalConstraints, and category (or stable properties like category.furnitures);
ensure any unstable objects used inside (globalConstraints, category) are stable
or referenced appropriately in the deps array. Update places where
toggleFurniture is passed to Chip onClick to use the memoized function.

In `@src/pages/imageSetup/steps/activityInfo/ActivityInfo.tsx`:
- Around line 57-62: selectionByNameEng currently hardcodes keys and your
useActivityInfo logic indexes categoriesData?.categories[0..3], which breaks if
the server changes order or adds categories; instead, build a dynamic map keyed
by each category's nameEng (e.g., create categoryByNameEng from
categoriesData.categories using nameEng) and refactor uses of selectionByNameEng
and any index-based access in useActivityInfo to look up categories via
categoryByNameEng[nameEng]; ensure null/undefined checks for categoriesData and
fallbacks where a nameEng is missing.

In `@src/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.css.ts`:
- Around line 22-40: The radioItem recipe currently only defines &:active and
includes redundant local button resets (border: 'none', font: 'inherit')—add an
explicit &:focus-visible selector on radioItem to provide a clear keyboard-focus
style (e.g., outline or box-shadow and visible offset), and remove the
duplicated global-reset properties (delete border: 'none' and font: 'inherit'
from radioItem) so the component relies on the shared global button reset; keep
selectors nested under radioItem and ensure the new focus-visible rule matches
the component's radius/padding variables for consistent visual styling.

In `@src/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.tsx`:
- Around line 29-31: The local state localSelected (initialized from
selectedActivityCode via useState in ActivityTypeSheet) isn’t synchronized when
the prop changes; add a useEffect that watches selectedActivityCode and updates
localSelected using setLocalSelected(selectedActivityCode) so the component
reflects external updates, taking care not to clobber in-progress user edits if
you need that (e.g., add a conditional guard if necessary).

In `@src/pages/imageSetup/steps/activityInfo/SelectTrigger.tsx`:
- Around line 19-44: SelectTrigger's trigger button lacks an aria-expanded
attribute; add a boolean prop (e.g., isOpen or isExpanded) to the SelectTrigger
component's props/interface and have callers pass the parent sheet/dropdown open
state, then set aria-expanded={!!isOpen} on the <button> element to expose the
expanded/collapsed state to assistive tech; update the component signature and
all usages to supply the state.

In `@src/pages/imageSetup/steps/interiorStyle/InteriorStyle.css.ts`:
- Around line 30-31: In the buttonWrapper style object replace the hardcoded
layout values used to compute right and bottom ("44rem" and "2rem") with design
tokens: pull the container width token (used instead of 44rem) and the spacing
token (used instead of 2rem) and use those tokens in the right and bottom
calculations inside buttonWrapper so changes to global tokens update this
positioning automatically; update any imports to bring in the token module and
ensure the token identifiers are used in the right: 'max(...)' calc expression
and bottom property in the InteriorStyle.css.ts file.

In `@src/pages/imageSetup/steps/interiorStyle/MoodboardCard.tsx`:
- Around line 32-33: 무드보드 카드의 <img src={src} alt={alt} className={styles.image}
draggable={false} /> 태그에 로딩 전략을 추가하세요: 기본적으로 loading="lazy"를 적용하되(성능 향상), 화면 상단에
항상 표시되는 카드용으로는 eager 로딩을 허용하도록 MoodboardCard 컴포넌트에 새로운 prop(예: isAboveTheFold 또는
loadingPriority)이나 기존 props를 사용해 조건부로 loading="eager"를 반환하도록 구현합니다; 구현 대상 식별자:
MoodboardCard 컴포넌트와 해당 <img ... /> JSX, props 이름은 isAboveTheFold 또는 loading 등으로
추가/사용하세요.

In `@src/pages/imageSetup/types/funnel/activityInfo.ts`:
- Around line 12-18: CATEGORY_SELECTION_MODE currently uses Record<string,
CategorySelectionMode>, which loses compile-time key safety; define an explicit
union type for the API key (e.g., type CategoryNameEng = 'BED' | 'SOFA' |
'TABLE' | 'SELECTIVE') and replace Record<string, CategorySelectionMode> with
Record<CategoryNameEng, CategorySelectionMode>, exporting the union type if
needed and updating any usage that expects the API's nameEng to use this new
CategoryNameEng type to catch typos at compile time.

In `@src/pages/imageSetup/types/funnel/steps.ts`:
- Around line 21-22: The payload/initialization still uses the old selectiveIds
field while the ActivityInfo type was renamed to furnitureIds; update the
ImageSetupPage component to stop initializing or sending selectiveIds and
instead use the new furnitureIds field (adjust initialization, state keys and
any payload construction in ImageSetupPage.tsx where selectiveIds is set/used),
ensuring consistency with the ActivityInfo type and any functions that read/send
that data.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 00acddc1-c67d-4958-8fff-8040df7f3e23

📥 Commits

Reviewing files that changed from the base of the PR and between 7d50596 and af7c65b.

⛔ Files ignored due to path filters (10)
  • src/shared/assets/v2/svg/BookBlack.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
  • src/shared/assets/v2/svg/BookGray.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
  • src/shared/assets/v2/svg/ChevronDownFill.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
  • src/shared/assets/v2/svg/CupBlack.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
  • src/shared/assets/v2/svg/CupGray.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
  • src/shared/assets/v2/svg/DeskBlack.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
  • src/shared/assets/v2/svg/DeskGray.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
  • src/shared/assets/v2/svg/Lock.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
  • src/shared/assets/v2/svg/MouseBlack.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
  • src/shared/assets/v2/svg/MouseGray.svg is excluded by !**/*.svg, !**/*.svg and included by src/**
📒 Files selected for processing (39)
  • src/main.tsx
  • src/mocks/handlers.ts
  • src/pages/imageSetup/apis/queries/useActivitiesQuery.ts
  • src/pages/imageSetup/apis/queries/useActivityOptionsQuery.ts
  • src/pages/imageSetup/apis/queries/useFurnitureCategoriesQuery.ts
  • src/pages/imageSetup/components/layout/FunnelLayout.tsx
  • src/pages/imageSetup/hooks/activityInfo/useActivityInfo.ts
  • src/pages/imageSetup/hooks/activityInfo/useActivitySelection.ts
  • src/pages/imageSetup/hooks/activityInfo/useCategorySelection.ts
  • src/pages/imageSetup/steps/activityInfo/ActivityInfo.css.ts
  • src/pages/imageSetup/steps/activityInfo/ActivityInfo.tsx
  • src/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.css.ts
  • src/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.tsx
  • src/pages/imageSetup/steps/activityInfo/SelectTrigger.css.ts
  • src/pages/imageSetup/steps/activityInfo/SelectTrigger.tsx
  • src/pages/imageSetup/steps/activityInfo/activityIcons.ts
  • src/pages/imageSetup/steps/interiorStyle/InteriorStyle.css.ts
  • src/pages/imageSetup/steps/interiorStyle/InteriorStyle.tsx
  • src/pages/imageSetup/steps/interiorStyle/MoodBoard.css.ts
  • src/pages/imageSetup/steps/interiorStyle/MoodBoard.tsx
  • src/pages/imageSetup/steps/interiorStyle/MoodboardCard.css.ts
  • src/pages/imageSetup/steps/interiorStyle/MoodboardCard.tsx
  • src/pages/imageSetup/stores/useFunnelStore.ts
  • src/pages/imageSetup/types/apis/activityInfo.ts
  • src/pages/imageSetup/types/funnel/activityInfo.ts
  • src/pages/imageSetup/types/funnel/steps.ts
  • src/pages/imageSetup/utils/staticDataPrefetch.ts
  • src/shared/components/card/cardImage/CardImage.css.ts
  • src/shared/components/card/cardImage/CardImage.tsx
  • src/shared/components/card/cardImage/SkeletonCardImage.tsx
  • src/shared/components/v2/bottomSheet/DragHandleBottomSheet.tsx
  • src/shared/components/v2/chip/Chip.css.ts
  • src/shared/components/v2/chip/Chip.tsx
  • src/shared/components/v2/icon/Icon.tsx
  • src/shared/components/v2/textHeading/TextHeading.css.ts
  • src/shared/constants/apiEndpoints.ts
  • src/shared/constants/queryKey.ts
  • src/stories/CardImage.stories.tsx
  • src/stories/SkeletonCardImage.stories.tsx
💤 Files with no reviewable changes (7)
  • src/pages/imageSetup/steps/interiorStyle/MoodBoard.css.ts
  • src/shared/components/card/cardImage/SkeletonCardImage.tsx
  • src/pages/imageSetup/apis/queries/useActivityOptionsQuery.ts
  • src/stories/SkeletonCardImage.stories.tsx
  • src/stories/CardImage.stories.tsx
  • src/shared/components/card/cardImage/CardImage.tsx
  • src/shared/components/card/cardImage/CardImage.css.ts

Comment thread src/main.tsx
Comment on lines +53 to +71
enableMocking().then(() => {
createRoot(rootElement, getSentryReactErrorHandlerOptions()).render(
// <StrictMode>
<ErrorBoundary FallbackComponent={AppErrorFallback}>
<HelmetProvider>
<QueryClientProvider client={queryClient}>
<OverlayProvider>
<App />
<ToastContainer {...toastConfig} />
{import.meta.env.DEV && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</OverlayProvider>
</QueryClientProvider>
</HelmetProvider>
</ErrorBoundary>
// </StrictMode>
);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

MSW 초기화 실패 시 앱이 렌더링되지 않는 경로가 있습니다.

현재는 enableMocking() 성공 시에만 render가 실행됩니다. 초기화 실패 시에도 앱은 뜨도록 fallback을 보장해야 합니다.

🛠️ 제안 수정안
-enableMocking().then(() => {
-  createRoot(rootElement, getSentryReactErrorHandlerOptions()).render(
+const renderApp = () => {
+  createRoot(rootElement, getSentryReactErrorHandlerOptions()).render(
     // <StrictMode>
     <ErrorBoundary FallbackComponent={AppErrorFallback}>
       <HelmetProvider>
         <QueryClientProvider client={queryClient}>
           <OverlayProvider>
@@
     </ErrorBoundary>
     // </StrictMode>
   );
-});
+};
+
+void enableMocking()
+  .catch((error) => {
+    console.error('MSW initialization failed', error);
+  })
+  .finally(() => {
+    renderApp();
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.tsx` around lines 53 - 71, The app currently only calls
createRoot(...).render(...) inside the enableMocking() promise success path, so
if enableMocking() rejects the app never mounts; change the flow to always call
createRoot(rootElement, getSentryReactErrorHandlerOptions()).render(...)
regardless of enableMocking() outcome by moving the render call out of the
then-only branch and handling enableMocking() errors separately (e.g., catch the
rejection, log the error, then proceed to render), referencing enableMocking(),
createRoot(...).render, rootElement, getSentryReactErrorHandlerOptions(), and
AppErrorFallback so the ErrorBoundary/HelmetProvider/QueryClientProvider tree is
mounted even when mocking initialization fails.

Comment on lines +15 to +22
type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo';

// 퍼널 스텝 별 NavBar 타이틀 매핑
const NAVBAR_TITLE_BY_STEP: Record<FunnelStepKey, string> = {
FloorPlanSelect: '공간 선택하기',
InteriorStyle: '취향 선택하기',
ActivityInfo: '가구 선택하기',
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

FunnelStepKey 타입 export 고려

FunnelStepKey 타입이 다른 퍼널 관련 컴포넌트에서도 사용될 수 있으므로, export 여부를 검토해주세요. 현재는 로컬 타입으로만 정의되어 있어요.

♻️ 타입 export 제안
-type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo';
+export type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo';
// 퍼널 스텝 별 NavBar 타이틀 매핑
const NAVBAR_TITLE_BY_STEP: Record<FunnelStepKey, string> = {
FloorPlanSelect: '공간 선택하기',
InteriorStyle: '취향 선택하기',
ActivityInfo: '가구 선택하기',
};
export type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo';
// 퍼널 스텝 별 NavBar 타이틀 매핑
const NAVBAR_TITLE_BY_STEP: Record<FunnelStepKey, string> = {
FloorPlanSelect: '공간 선택하기',
InteriorStyle: '취향 선택하기',
ActivityInfo: '가구 선택하기',
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/imageSetup/components/layout/FunnelLayout.tsx` around lines 15 -
22, The FunnelStepKey type is currently local but should be exported so other
funnel-related components can reuse it; change the declaration to export the
type (export type FunnelStepKey = ...) and keep NAVBAR_TITLE_BY_STEP as
Record<FunnelStepKey, string> to preserve typings, then update any consumers to
import { FunnelStepKey } from this module where needed.

Comment on lines 96 to 123
const bed = useCategorySelection(
activityOptionsData?.categories[0] || null,
categoriesData?.categories[0] ?? null,
CATEGORY_SELECTION_MODE.BED,
formData,
setFormData,
globalConstraints
);
const sofa = useCategorySelection(
activityOptionsData?.categories[1] || null,
formData,
setFormData,
globalConstraints
);
const storage = useCategorySelection(
activityOptionsData?.categories[2] || null,
categoriesData?.categories[1] ?? null,
CATEGORY_SELECTION_MODE.SOFA,
formData,
setFormData,
globalConstraints
);
const table = useCategorySelection(
activityOptionsData?.categories[3] || null,
categoriesData?.categories[2] ?? null,
CATEGORY_SELECTION_MODE.TABLE,
formData,
setFormData,
globalConstraints
);
const selective = useCategorySelection(
activityOptionsData?.categories[4] || null,
categoriesData?.categories[3] ?? null,
CATEGORY_SELECTION_MODE.SELECTIVE,
formData,
setFormData,
globalConstraints
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

카테고리 인덱스 기반 접근의 잠재적 취약점

categoriesData?.categories[0], [1], [2], [3]으로 고정 인덱스 접근하고 있어요. 서버에서 카테고리 순서가 변경되거나 일부 카테고리가 누락되면 잘못된 매핑이 발생할 수 있어요.

nameEng 기준으로 동적 매핑하면 더 안전해요.

🔧 nameEng 기반 동적 매핑 제안
const getCategoryByNameEng = (nameEng: string) =>
  categoriesData?.categories.find((c) => c.nameEng === nameEng) ?? null;

const bed = useCategorySelection(
  getCategoryByNameEng('BED'),
  CATEGORY_SELECTION_MODE.BED,
  // ...
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/imageSetup/hooks/activityInfo/useActivityInfo.ts` around lines 96 -
123, The code currently indexes categoriesData?.categories by fixed positions
when calling useCategorySelection (e.g., categoriesData?.categories[0]) which
can mis-map if the server changes order or omits items; replace those
fixed-index calls by first implementing a helper like
getCategoryByNameEng(nameEng) that returns categoriesData?.categories.find(c =>
c.nameEng === nameEng) ?? null, then call useCategorySelection with
getCategoryByNameEng('BED'), getCategoryByNameEng('SOFA'),
getCategoryByNameEng('TABLE'), and getCategoryByNameEng('SELECTIVE')
respectively, keeping the same CATEGORY_SELECTION_MODE.*, formData, setFormData
and globalConstraints parameters and preserving null fallback behavior.

Comment on lines 148 to +168
useEffect(() => {
// Zustand에 저장된 데이터가 있으면 해당 데이터 유지
if (savedActivityInfo?.activityType === formData.activityType) {
// Zustand에 저장된 데이터가 있으면 해당 데이터 유지 (복원 케이스 스킵)
if (savedActivityInfo?.activity === formData.activity) {
return;
}

if (formData.activityType) {
if (formData.activity) {
const requiredIds = activitySelection.getRequiredFurnitureIds();
setFormData((prev) => ({
...prev,
selectiveIds: requiredIds,
furnitureIds: requiredIds,
}));
} else {
// 주요활동이 해제된 경우 모든 가구 선택 해제
setFormData((prev) => ({
...prev,
selectiveIds: [],
furnitureIds: [],
}));
}
}, [formData.activityType, savedActivityInfo]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formData.activity, savedActivityInfo]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

eslint-disable 주석과 의존성 배열 검토

activitySelection.getRequiredFurnitureIds가 의존성 배열에 없어서 stale closure 이슈가 있을 수 있어요. 현재는 formData.activity 변경 시 activitySelection도 새로 계산되므로 동작에는 문제없지만, 명시적으로 의존성에 추가하거나 주석으로 이유를 남기면 좋아요.

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

In `@src/pages/imageSetup/hooks/activityInfo/useActivityInfo.ts` around lines 148
- 168, The useEffect may suffer a stale-closure because
activitySelection.getRequiredFurnitureIds is not listed in the dependency array;
update the effect to include either activitySelection or
activitySelection.getRequiredFurnitureIds in the dependencies (instead of
silencing eslint) so the effect re-runs when the selector changes, or if you
deliberately rely on formData.activity driving recalculation, replace the
eslint-disable with a short comment in useActivityInfo explaining that
activitySelection is derived from formData.activity and therefore intentionally
omitted; touch the effect around useEffect and references to
activitySelection.getRequiredFurnitureIds and savedActivityInfo to make the
intent explicit.

Comment thread src/pages/imageSetup/hooks/activityInfo/useCategorySelection.ts
Comment on lines +19 to +44
return (
<button type="button" className={styles.trigger} onClick={onClick}>
<div className={styles.leftContainer}>
{activityIconName && <Icon name={activityIconName} size="20" />}
{selectedActivity ? (
<div className={styles.labelContainer}>
<span className={styles.selectedLabel}>
{selectedActivity.label}
</span>
{requiredFurnitureLabel && (
<>
<span className={styles.divider} />
<span className={styles.requiredLabel}>
{requiredFurnitureLabel}
</span>
</>
)}
</div>
) : (
<span className={styles.placeholderLabel}>
활동 형태를 선택해주세요
</span>
)}
</div>
<Icon name="ChevronDownFill" size="20" />
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

접근성: aria-expanded 속성 추가 권장

드롭다운/바텀시트 트리거 버튼에 aria-expanded 속성을 추가하면 스크린 리더 사용자에게 현재 상태를 명확히 전달할 수 있어요. 부모 컴포넌트에서 시트 열림 상태를 전달받아 적용하는 것을 고려해주세요.

♻️ 접근성 개선 제안
 interface SelectTriggerProps {
   selectedActivity?: ActivityItem;
   onClick: () => void;
+  isOpen?: boolean;
 }

-const SelectTrigger = ({ selectedActivity, onClick }: SelectTriggerProps) => {
+const SelectTrigger = ({ selectedActivity, onClick, isOpen = false }: SelectTriggerProps) => {
   // ...
   return (
-    <button type="button" className={styles.trigger} onClick={onClick}>
+    <button
+      type="button"
+      className={styles.trigger}
+      onClick={onClick}
+      aria-expanded={isOpen}
+      aria-haspopup="dialog"
+    >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/imageSetup/steps/activityInfo/SelectTrigger.tsx` around lines 19 -
44, SelectTrigger's trigger button lacks an aria-expanded attribute; add a
boolean prop (e.g., isOpen or isExpanded) to the SelectTrigger component's
props/interface and have callers pass the parent sheet/dropdown open state, then
set aria-expanded={!!isOpen} on the <button> element to expose the
expanded/collapsed state to assistive tech; update the component signature and
all usages to supply the state.

Comment thread src/pages/imageSetup/steps/interiorStyle/InteriorStyle.css.ts
Comment thread src/pages/imageSetup/steps/interiorStyle/MoodboardCard.tsx
Comment thread src/pages/imageSetup/types/funnel/activityInfo.ts
Comment thread src/pages/imageSetup/types/funnel/steps.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💫 Feature 기능 개발 🛠️ Refactor 리팩토링 🤙 지성 웹 36기 임지성

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor] 주요활동 리뉴얼

1 participant