Skip to content

Conversation

@yoouyeon
Copy link
Contributor

@yoouyeon yoouyeon commented Jan 13, 2026

hotfix 변경사항 동기화

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • Storybook 스토리 대거 추가로 UI 컴포넌트 미리보기 확대
    • 로그인 입장 화면과 독립 프로필 이미지 컴포넌트 추가
  • 스타일

    • 홈·청구·멤버설정 등 여러 페이지의 레이아웃 간격·패딩 조정
    • 일부 컴포넌트의 시각적 정렬 및 텍스트 스타일 개선
  • 리팩터링

    • 멤버 프로필 컴포넌트 인터페이스 단순화 및 사용 방식 변경
  • Chores

    • Storybook 및 관련 패키지 업데이트, MSW 통합 정비

✏️ Tip: You can customize this high-level summary in your review settings.

fix: Add missing semicolon in Storybook preview configuration
개발 서버 배포 전 테스트를 위해 일부 API를 mock으로 사용하도록 설정
- 내부 모듈 import 문제 해결
- 관련된 의존성 함께 조정

참고: storybookjs/storybook#30335
- Flex의 gap, padding 계열 프로퍼티를 사용하는 컴포넌트 스토리 정의
- Chromatic 스냅샷 대상 설정
- 스냅샷 변경 여부에 따라 PR 코멘트 분기
- Storybook과 빌드 링크 포함
- CurvedProgressBar 간격 처리를 빈 요소 대신 margin으로 변경
- 시간 표시 레이아웃을 flex 중첩에서 grid로 단순화
- Flex spacing 단위를 디자인 시스템 기준으로 수정
- 불필요한 Flex 래퍼 제거
- Flex spacing 단위를 디자인 시스템 기준으로 수정
- Flex 컴포넌트의 gap 및 padding 단위를 디자인 시스템 기준으로 수정
- 텍스트 요소의 스타일을 inline-block으로 변경하여 레이아웃 개선
- 진행중인 정산 섹션의 Flex 속성 조정
- bgColor를 디자인 시스템 기준으로 수정
- Flex spacing 단위 정리
- DescriptionField title 구조 단순화
- api 요청 경로를 api에서 functions로 변경
- 토큰 발급 요청에 apikey 헤더 포함하도록 설정 추가
- 크기별 프로필 이미지 컴포넌트 추가
- 스토리 추가
- profile, isPaid, paidAt 타입을 옵셔널로 변경 (falsy값 처리가 되어 있음)
- 관련 파일 수정
리뷰: #16 (comment)
- MemberProfile 컴포넌트에서 필요한 props만 받도록 수정
- 타입 변경사항 반영

...
다른 타입과의 일관성을 유지하기 위함
리뷰: #16 (comment)
@yoouyeon yoouyeon self-assigned this Jan 13, 2026
@yoouyeon yoouyeon added 🎨 Design 디자인 관련 작업 🔥 Hotfix 긴급 수정 labels Jan 13, 2026
@github-actions github-actions bot requested a review from ongheong January 13, 2026 16:33
@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

이 PR은 MSW Storybook 애드온을 통합하고 MemberProfile 컴포넌트의 props를 분리·리팩토링하며, 여러 페이지의 레이아웃/스타일 값을 조정합니다. CI/CD에 VITE_SUPABASE_PUBLIC_KEY를 추가하고 axios의 baseURL을 functions/v1로 변경하며 다수의 Storybook 스토리 파일을 추가합니다.

Changes

Cohort / File(s) 요약
CI/CD 및 워크플로우
​.github/workflows/prod-ci-cd.yml, ​.github/workflows/publish-storybook.yml
빌드 환경에 VITE_SUPABASE_PUBLIC_KEY 추가. publish-storybook의 PR 코멘트 단계를 chromatic.changeCount 조건으로 분리 및 메시지 블록화(포맷 변경).
Storybook 설정 및 의존성
.storybook/preview.ts, package.json
msw-storybook-addon 초기화 및 mswLoader 추가, Chromatic 스냅샷 플래그 조정. @storybook/react/storybook 업그레이드 및 msw-storybook-addon 의존성 추가.
MemberProfile 리팩토링 + 이미지 컴포넌트 추가
src/shared/ui/MemberProfile/index.tsx, src/shared/ui/MemberProfile/index.stories.ts, src/shared/ui/MemberProfileImage/*
MemberProfile props를 member 객체에서 id, name, profile, canDelete 필드로 변경. 신규 MemberProfileImage 컴포넌트 및 스타일/스토리 추가. 관련 스토리북 스토리 추가.
소비자 컴포넌트 업데이트(프로필 관련)
src/features/expense-management/ui/MemberExpenses/index.*, src/pages/billDetail/ui/ExpenseMemberItem/index.*
인라인 ProfileImg/관련 스타일 제거, MemberProfile/MemberProfileImage로 대체. 스타일 선언 제거.
페이지 레이아웃·스타일 조정(다수)
src/pages/home/HomePage.tsx, src/pages/home/ui/HomeExpenseItem/index.tsx, src/pages/selectGroup/SelectGroupPage.tsx, src/pages/memberSetup/ui/AddMember/index.tsx, src/pages/addAccountStep/ui/BankNameDrawer/index.tsx, src/pages/billDetail/ui/CurvedProgressBar/index.style.ts
패딩/마진/간격 값 변경(예: 5→20 등), 불필요한 래퍼 제거, DOM 구조 및 Flex 사용 확대.
ExpenseTimeHeader 리팩토링
src/pages/billDetail/ui/ExpenseTimeHeader/index.style.ts, src/pages/billDetail/ui/ExpenseTimeHeader/index.tsx, src/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsx
Timer 전용 스타일 추가, ExpenseChip 스타일 오버라이드 제거, 타이머 렌더링 구조 단순화 및 라벨 추가. MSW 로더와 스토리 테스트 추가.
로그인 엔트런스 분리
src/pages/login/LoginEntranceView.*, src/pages/login/LoginPage.*, src/pages/login/LoginPage.styles.ts
엔트런스 UI를 LoginEntranceView로 분리하여 새 컴포넌트/스타일 추가 및 LoginPage에서 대체. 기존 스타일(일부) 제거.
Storybook 스토리 추가(여러 컴포넌트)
src/pages/*/*.stories.tsx, src/shared/ui/MemberProfile/index.stories.ts, src/shared/ui/MemberProfileImage/index.stories.ts
여러 페이지/컴포넌트에 대한 Storybook 메타·스토리 추가(Chromatic params, MSW 핸들러, 라우터/Query 데코레이터 포함).
공유 UI 개선
src/shared/ui/Flex/index.tsx, src/shared/ui/Text/index.tsx
Flex에 JSDoc 추가. Text 컴포넌트에 style?: CSSProperties prop 추가 및 전달.
API 및 axios 구성 변경
src/shared/api/axios.ts
기본 baseURL.../api/v1.../functions/v1로 변경. 특정 엔드포인트(user/guest/token) 요청에 apikey 헤더로 VITE_SUPABASE_PUBLIC_KEY 추가. Mock 경로 처리 및 기존 인증 헤더 로직 유지.

Possibly related PRs

Suggested labels

⚙️ CI/CD, 🔨 Refactor

Poem

🐰 새 프로필엔 깃털 대신 컴포넌트,
간격 맞춰 도약하니 UI가 쨍,
스토리북에선 MSW 춤추고,
CI엔 키를 심어 빌드가 반짝,
모또의 토끼가 둥실 축하해요! 🎉

🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@yoouyeon yoouyeon removed the request for review from ongheong January 13, 2026 16:33
@coderabbitai coderabbitai bot changed the title @coderabbitai 제목 Storybook MSW 애드온 통합 및 컴포넌트 분리 Jan 13, 2026
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: 3

Caution

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

⚠️ Outside diff range comments (2)
src/shared/api/axios.ts (1)

7-10: 초기화 시점에 localStorage.getItem 호출은 문제가 될 수 있습니다.

모듈이 로드될 때 localStorage.getItem('accessToken')이 호출되는데, 이 시점에는 토큰이 아직 설정되지 않았을 수 있습니다. 다행히 Line 18-21의 인터셉터에서 최신 토큰으로 덮어쓰므로 기능상 문제는 없지만, 초기 headers 설정에서 null이 문자열화되어 "null"로 설정될 수 있습니다.

🔧 제안된 수정
 const axiosInstance = axios.create({
   baseURL: `${import.meta.env.VITE_SERVER_URL}/functions/v1`,
   withCredentials: true,
   headers: {
     'Content-Type': 'application/json',
-    Authorization: `${localStorage.getItem('accessToken')}`,
   },
 });

인터셉터에서 이미 토큰을 설정하므로 초기 headers에서 Authorization을 제거해도 됩니다.

package.json (1)

53-60: Storybook 패키지 버전 불일치를 확인해 주세요.

storybook@storybook/react^8.6.12로 업데이트되었지만, 나머지 Storybook 애드온들(@storybook/addon-essentials, @storybook/addon-interactions, @storybook/addon-onboarding, @storybook/blocks, @storybook/react-vite, @storybook/test)은 ^8.5.0이고, @storybook/addon-themes^8.5.2입니다. 호환성 문제를 방지하기 위해 모든 Storybook 관련 패키지를 동일한 메이저/마이너 버전으로 통일하는 것을 권장합니다.

🤖 Fix all issues with AI agents
In @src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx:
- Around line 15-19: The Default story for BankNameDrawer is missing required
props, causing potential TypeScript/runtime errors; update the Story object for
Default to supply the required onClose and setBankName props (either real
handler functions or Storybook actions) so BankNameDrawer receives onClose and
setBankName when rendered; locate the Default export in index.stories.tsx and
add these props to args for the BankNameDrawer story (or wire Storybook action
handlers) to satisfy the component's required props.

In @src/shared/api/axios.ts:
- Around line 30-36: The endsWith check on newConfig.url can fail when query
params exist; update the condition in the Axios request-config handling (where
newConfig.url?.endsWith('user/guest/token') is used) to compare only the URL
path portion — e.g., derive the path via new URL(newConfig.url,
'http://a').pathname or newConfig.url.split('?')[0] and then call
.endsWith('user/guest/token'), so the apikey header logic still runs when query
parameters are present.

In @src/shared/ui/MemberProfile/index.stories.ts:
- Around line 14-28: The stories for MemberProfile are missing the required
handleDeleteButtonClick prop, causing render/runtime errors; update the
ManagerProfile and ParticipantProfile Story args to include a
handleDeleteButtonClick entry (e.g., a no-op function or Storybook action) so
the MemberProfile component receives that required callback when rendered;
ensure the prop name exactly matches handleDeleteButtonClick used by
MemberProfile so both stories pass the prop.
🧹 Nitpick comments (10)
.github/workflows/publish-storybook.yml (1)

60-60: 파일 끝에 개행 문자 누락.

POSIX 표준 및 대부분의 린터는 파일 끝에 개행 문자를 권장합니다.

🔧 제안된 수정
             ✅ 스냅샷 변경 없음
+
src/shared/ui/MemberProfileImage/index.tsx (1)

4-10: size prop이 필수인데 fallback이 있습니다.

size prop은 인터페이스에서 필수(size: 'sm' | 'md' | 'lg')로 정의되어 있으므로, Line 10의 size || 'md' fallback은 불필요합니다. TypeScript가 이미 값을 보장합니다.

♻️ 제안된 수정
 function MemberProfileImage({ src, size }: MemberProfileImageProps) {
-  return <S.Image src={src || defaultProfileImg} $size={size || 'md'} />;
+  return <S.Image src={src || defaultProfileImg} $size={size} />;
 }
src/shared/ui/MemberProfileImage/index.styles.ts (1)

13-18: object-fit: contain 대신 cover 사용을 고려해 주세요.

프로필 이미지의 경우 object-fit: contain을 사용하면 이미지 비율에 따라 원형 영역 내에 여백이 생길 수 있습니다. 일반적으로 프로필 이미지는 object-fit: cover를 사용하여 원형 영역을 완전히 채우는 것이 더 자연스럽습니다.

♻️ 제안하는 수정
 export const Image = styled.img<ProfileImgProps>`
   width: ${(props) => sizeMap[props.$size]};
   height: ${(props) => sizeMap[props.$size]};
-  object-fit: contain;
+  object-fit: cover;
   border-radius: 50%;
 `;
src/shared/ui/MemberProfileImage/index.stories.ts (1)

16-35: 실제 이미지 URL을 포함한 스토리 추가를 고려해 주세요.

현재 모든 스토리에서 src: ''를 사용하고 있어 기본/대체 이미지만 테스트됩니다. 실제 이미지 URL을 사용하는 스토리를 추가하면 이미지 로딩 및 렌더링 동작을 더 완전하게 검증할 수 있습니다.

♻️ 제안하는 추가 스토리
export const WithImage: Story = {
  args: {
    src: 'https://via.placeholder.com/150',
    size: 'md',
  },
};
src/shared/ui/MemberProfile/index.tsx (1)

23-30: key prop이 불필요합니다.

key prop은 리스트 렌더링 시 부모 컴포넌트에서 사용됩니다. MemberProfile 내부의 Flex에 전달된 key={id}는 효과가 없습니다. 실제로 부모 컴포넌트들(AddMember, MemberExpenses)에서 이미 key={member.id}를 사용하고 있습니다.

🧹 선택적 수정
     <Flex
-      key={id}
       gap={4}
       direction="column"
       alignItems="center"
       width="fit-content"
       py={8}
     >
src/pages/selectGroup/SelectGroupPage.stories.tsx (2)

15-15: 스토리 간 상태 누수 가능성이 있습니다.

QueryClient가 모듈 레벨에서 생성되어 스토리 간에 공유됩니다. 이로 인해 캐시된 데이터가 다른 스토리에 영향을 줄 수 있습니다.

♻️ 데코레이터 내부에서 QueryClient 생성
-const queryClient = new QueryClient();
-
 const meta: Meta<typeof SelectGroupPage> = {
   title: 'pages/SelectGroupPage',
   component: SelectGroupPage,
   parameters: {
     chromatic: { disableSnapshot: true },
     msw: {
       handlers: [
         http.get('http://localhost:3000/api/v1/group/header', () => {
           return HttpResponse.json({
             groupName: '모또 정기모임',
             totalAmount: 150000,
             deadline: '2025-12-26T23:59:59Z',
             bank: '국민은행',
             accountNumber: '123456-78-910111',
           });
         }),
       ],
     },
   },
   decorators: [
     (Story) => {
+      const queryClient = new QueryClient({
+        defaultOptions: {
+          queries: {
+            retry: false,
+          },
+        },
+      });
       const mockRouter = createMemoryRouter([
         {
           path: '/*',
           element: <Story />,
           loader: () => ({ groupToken: 'mock-group-token' }),
         },
       ]);
       return (
         <QueryClientProvider client={queryClient}>
           <RouterProvider router={mockRouter} />
         </QueryClientProvider>
       );
     },
   ],
 };

24-24: 하드코딩된 API URL 사용에 주의하세요.

http://localhost:3000이 하드코딩되어 있습니다. API base URL이 변경되면 MSW 핸들러가 올바르게 동작하지 않을 수 있습니다. 환경 변수나 상대 경로 사용을 고려해 보세요.

src/pages/selectGroup/SelectGroupPage.tsx (1)

41-41: pt prop의 타입을 확인하세요.

pt="6"이 문자열로 전달되고 있습니다. 학습된 가이드라인에 따르면 Flex 컴포넌트의 spacing props는 숫자(pt={6})로 전달되어야 합니다.

♻️ 숫자 타입으로 변경
-      pt="6"
+      pt={6}
src/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsx (2)

16-16: QueryClient가 모듈 레벨에서 생성되어 스토리 간 캐시 오염이 발생할 수 있습니다.

현재 queryClient가 파일 최상단에서 한 번만 생성되어 모든 스토리에서 공유됩니다. 이로 인해 스토리 간 쿼리 캐시가 공유되어 테스트가 불안정해질 수 있습니다.

♻️ 데코레이터 내부에서 QueryClient 생성
-const queryClient = new QueryClient();
-
 const meta: Meta<typeof ExpenseTimeHeader> = {
   title: 'ui/ExpenseTimeHeader',
   component: ExpenseTimeHeader,
   parameters: {
     chromatic: { disableSnapshot: false },
     msw: {
       handlers: [
         http.get('http://localhost:3000/api/v1/group/header', () => {
           return HttpResponse.json({
             groupName: '모또 정기모임',
             totalAmount: 150000,
             deadline: '2025-12-26T23:59:59Z',
             bank: '국민은행',
             accountNumber: '123456-78-910111',
           });
         }),
       ],
     },
   },
   decorators: [
     (Story) => {
+      const queryClient = new QueryClient({
+        defaultOptions: {
+          queries: {
+            retry: false,
+          },
+        },
+      });
       const mockRouter = createMemoryRouter([
         {
           path: '/*',
           element: <Story />,
           loader: () => ({ groupToken: 'mock-group-token' }),
         },
       ]);
       return (
         <QueryClientProvider client={queryClient}>
           <RouterProvider router={mockRouter} />
         </QueryClientProvider>
       );
     },
   ],
 };

65-70: play 함수에서 findByText 사용을 권장합니다.

waitFor 내부에서 getByText를 사용하는 대신, 비동기 쿼리인 findByText를 직접 사용하면 더 간결합니다.

♻️ findByText 사용
   play: async ({ canvasElement }) => {
     const canvas = within(canvasElement);
-    await waitFor(() => {
-      canvas.getByText('정산을 완료해주세요');
-    });
+    await canvas.findByText('정산을 완료해주세요');
   },
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 739f485 and 544e48f.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (34)
  • .github/workflows/prod-ci-cd.yml
  • .github/workflows/publish-storybook.yml
  • .storybook/preview.ts
  • package.json
  • src/features/expense-management/ui/MemberExpenses/index.styles.ts
  • src/features/expense-management/ui/MemberExpenses/index.tsx
  • src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx
  • src/pages/addAccountStep/ui/BankNameDrawer/index.tsx
  • src/pages/billDetail/ui/CurvedProgressBar/index.style.ts
  • src/pages/billDetail/ui/ExpenseMemberItem/index.style.ts
  • src/pages/billDetail/ui/ExpenseMemberItem/index.tsx
  • src/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsx
  • src/pages/billDetail/ui/ExpenseTimeHeader/index.style.ts
  • src/pages/billDetail/ui/ExpenseTimeHeader/index.tsx
  • src/pages/home/HomePage.stories.tsx
  • src/pages/home/HomePage.tsx
  • src/pages/home/ui/HomeExpenseItem/index.tsx
  • src/pages/login/LoginEntranceView.stories.tsx
  • src/pages/login/LoginEntranceView.styles.tsx
  • src/pages/login/LoginEntranceView.tsx
  • src/pages/login/LoginPage.styles.ts
  • src/pages/login/LoginPage.tsx
  • src/pages/memberSetup/ui/AddMember/index.stories.tsx
  • src/pages/memberSetup/ui/AddMember/index.tsx
  • src/pages/selectGroup/SelectGroupPage.stories.tsx
  • src/pages/selectGroup/SelectGroupPage.tsx
  • src/shared/api/axios.ts
  • src/shared/ui/Flex/index.tsx
  • src/shared/ui/MemberProfile/index.stories.ts
  • src/shared/ui/MemberProfile/index.tsx
  • src/shared/ui/MemberProfileImage/index.stories.ts
  • src/shared/ui/MemberProfileImage/index.styles.ts
  • src/shared/ui/MemberProfileImage/index.tsx
  • src/shared/ui/Text/index.tsx
💤 Files with no reviewable changes (3)
  • src/pages/login/LoginPage.styles.ts
  • src/features/expense-management/ui/MemberExpenses/index.styles.ts
  • src/pages/billDetail/ui/ExpenseMemberItem/index.style.ts
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-12-28T04:24:56.857Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 12
File: src/pages/login/LoginEntranceView.tsx:13-20
Timestamp: 2025-12-28T04:24:56.857Z
Learning: Guideline: Do not pass raw numbers for spacing props (e.g., gap, padding, margin) to the Flex component. If the design system uses tokenized spacing (theme.unit[...] ), derive spacing from design tokens instead of hard-coded numbers only if the component expects a direct number. In this case, the correct usage is to pass plain numbers (e.g., 16) directly for spacing props used by Flex, rather than theme.unit[16]. Apply this consistently across TSX files where Flex spacing props are used. This improves readability and ensures consistent spacing values align with design tokens.

Applied to files:

  • src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx
  • src/pages/billDetail/ui/CurvedProgressBar/index.style.ts
  • src/shared/ui/MemberProfileImage/index.stories.ts
  • src/shared/ui/Text/index.tsx
  • src/pages/login/LoginEntranceView.styles.tsx
  • src/pages/memberSetup/ui/AddMember/index.stories.tsx
  • src/pages/home/HomePage.stories.tsx
  • src/shared/ui/Flex/index.tsx
  • src/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsx
  • src/shared/ui/MemberProfile/index.stories.ts
  • src/pages/login/LoginEntranceView.stories.tsx
  • src/pages/login/LoginEntranceView.tsx
  • src/pages/addAccountStep/ui/BankNameDrawer/index.tsx
  • src/shared/api/axios.ts
  • src/pages/billDetail/ui/ExpenseMemberItem/index.tsx
  • src/pages/selectGroup/SelectGroupPage.stories.tsx
  • src/pages/billDetail/ui/ExpenseTimeHeader/index.tsx
  • src/shared/ui/MemberProfileImage/index.styles.ts
  • src/pages/home/ui/HomeExpenseItem/index.tsx
  • src/shared/ui/MemberProfileImage/index.tsx
  • src/pages/home/HomePage.tsx
  • src/pages/selectGroup/SelectGroupPage.tsx
  • src/features/expense-management/ui/MemberExpenses/index.tsx
  • src/pages/login/LoginPage.tsx
  • src/pages/billDetail/ui/ExpenseTimeHeader/index.style.ts
  • src/pages/memberSetup/ui/AddMember/index.tsx
  • src/shared/ui/MemberProfile/index.tsx
📚 Learning: 2025-09-21T07:58:28.422Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 7
File: src/features/expense-management/ui/NumberInput/index.tsx:2-2
Timestamp: 2025-09-21T07:58:28.422Z
Learning: src/shared/ui/Text/index.tsx 파일에는 export default Text; 가 35번째 줄에 존재한다. import Text from '@/shared/ui/Text' 구문이 올바르게 작동한다.

Applied to files:

  • src/shared/ui/Text/index.tsx
  • src/pages/login/LoginEntranceView.tsx
  • src/pages/home/ui/HomeExpenseItem/index.tsx
📚 Learning: 2025-09-21T07:58:28.422Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 7
File: src/features/expense-management/ui/NumberInput/index.tsx:2-2
Timestamp: 2025-09-21T07:58:28.422Z
Learning: src/shared/ui/Text/index.tsx 파일에는 default export가 존재한다 (export default Text;). import Text from '@/shared/ui/Text' 구문이 올바르게 작동한다.

Applied to files:

  • src/shared/ui/Text/index.tsx
  • src/pages/login/LoginEntranceView.tsx
  • src/pages/home/ui/HomeExpenseItem/index.tsx
📚 Learning: 2025-12-28T04:21:15.172Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 12
File: src/pages/login/LoginEntranceView.styles.tsx:21-26
Timestamp: 2025-12-28T04:21:15.172Z
Learning: LoginEntranceView.styles.tsx와 LoginPage.styles.ts의 LogoImg styled-component는 상태에 따라 분기되는 독립적인 UI에서 사용되며, 미래에 각각 다르게 변경될 가능성을 고려하여 의도적으로 중복 선언되었다. 이러한 경우 공통 모듈 추출보다 중복 유지가 더 적절한 설계이다.

Applied to files:

  • src/pages/login/LoginEntranceView.styles.tsx
  • src/pages/login/LoginEntranceView.tsx
  • src/shared/ui/MemberProfileImage/index.styles.ts
  • src/pages/login/LoginPage.tsx
📚 Learning: 2025-09-21T08:03:27.190Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 7
File: src/features/expense-management/ui/FormField/index.tsx:11-13
Timestamp: 2025-09-21T08:03:27.190Z
Learning: src/shared/ui/Button/index.tsx (Line 20), src/shared/ui/Text/index.tsx (Line 35), src/shared/ui/Input/index.tsx (Line 59) 파일들에는 모두 export default가 존재한다. default import 방식 (import Button from '@/shared/ui/Button' 등)이 올바르게 작동한다.

Applied to files:

  • src/pages/login/LoginEntranceView.tsx
  • src/pages/home/ui/HomeExpenseItem/index.tsx
  • src/pages/login/LoginPage.tsx
📚 Learning: 2025-09-21T08:03:27.190Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 7
File: src/features/expense-management/ui/FormField/index.tsx:11-13
Timestamp: 2025-09-21T08:03:27.190Z
Learning: src/shared/ui/Button/index.tsx, src/shared/ui/Text/index.tsx, src/shared/ui/Input/index.tsx 파일들에는 모두 export default가 존재한다. default import 방식 (import Button from '@/shared/ui/Button' 등)이 올바르게 작동한다.

Applied to files:

  • src/pages/login/LoginEntranceView.tsx
  • src/pages/home/ui/HomeExpenseItem/index.tsx
🧬 Code graph analysis (11)
src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx (5)
src/pages/memberSetup/ui/AddMember/index.stories.tsx (1)
  • Default (49-54)
src/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsx (1)
  • Default (58-71)
src/pages/home/HomePage.stories.tsx (1)
  • Default (27-27)
src/pages/login/LoginEntranceView.stories.tsx (1)
  • Default (15-15)
src/pages/selectGroup/SelectGroupPage.stories.tsx (1)
  • Default (57-57)
src/pages/login/LoginEntranceView.styles.tsx (1)
src/pages/login/LoginPage.styles.ts (1)
  • LogoImg (3-8)
src/pages/home/HomePage.stories.tsx (5)
src/pages/memberSetup/ui/AddMember/index.stories.tsx (1)
  • Default (49-54)
src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx (1)
  • Default (15-19)
src/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsx (1)
  • Default (58-71)
src/pages/login/LoginEntranceView.stories.tsx (1)
  • Default (15-15)
src/pages/selectGroup/SelectGroupPage.stories.tsx (1)
  • Default (57-57)
src/pages/login/LoginEntranceView.stories.tsx (4)
src/pages/memberSetup/ui/AddMember/index.stories.tsx (1)
  • Default (49-54)
src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx (1)
  • Default (15-19)
src/pages/home/HomePage.stories.tsx (1)
  • Default (27-27)
src/pages/selectGroup/SelectGroupPage.stories.tsx (1)
  • Default (57-57)
src/pages/login/LoginEntranceView.tsx (4)
src/pages/login/LoginEntranceView.styles.tsx (1)
  • LogoImg (21-26)
src/pages/login/LoginPage.styles.ts (1)
  • LogoImg (3-8)
src/shared/ui/Text/index.styles.tsx (1)
  • Text (20-25)
src/shared/ui/Lottie/index.tsx (1)
  • CoinLottie (9-15)
src/pages/selectGroup/SelectGroupPage.stories.tsx (5)
src/pages/memberSetup/ui/AddMember/index.stories.tsx (1)
  • Default (49-54)
src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx (1)
  • Default (15-19)
src/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsx (1)
  • Default (58-71)
src/pages/home/HomePage.stories.tsx (1)
  • Default (27-27)
src/pages/login/LoginEntranceView.stories.tsx (1)
  • Default (15-15)
src/pages/billDetail/ui/ExpenseTimeHeader/index.tsx (1)
src/shared/ui/Text/index.styles.tsx (1)
  • Text (20-25)
src/pages/home/ui/HomeExpenseItem/index.tsx (1)
src/shared/ui/Text/index.styles.tsx (1)
  • Text (20-25)
src/pages/home/HomePage.tsx (3)
src/pages/error/ErrorPage.style.ts (1)
  • Flex (8-15)
src/pages/notFound/NotFoundPage.style.ts (1)
  • Flex (8-15)
src/shared/ui/Text/index.styles.tsx (1)
  • Text (20-25)
src/pages/memberSetup/ui/AddMember/index.tsx (2)
src/shared/ui/Input/index.styles.ts (1)
  • Input (81-90)
src/shared/ui/Text/index.styles.tsx (1)
  • Text (20-25)
src/shared/ui/MemberProfile/index.tsx (1)
src/shared/ui/Text/index.styles.tsx (1)
  • Text (20-25)
🔇 Additional comments (34)
src/pages/billDetail/ui/CurvedProgressBar/index.style.ts (1)

3-8: LGTM!

styled-components에서 theme.unit[20]을 사용한 margin-top 적용이 디자인 시스템 토큰 사용 방식에 적합합니다.

src/pages/addAccountStep/ui/BankNameDrawer/index.tsx (1)

24-31: LGTM!

Flex 컴포넌트의 px prop에 plain number(20)를 사용한 것이 디자인 시스템 가이드라인에 부합합니다. 수평 패딩 증가로 UI 여백이 개선됩니다.

src/shared/ui/Flex/index.tsx (1)

9-15: LGTM!

JSDoc 문서화가 Flex 컴포넌트의 spacing prop 사용법을 명확히 설명합니다. 숫자 토큰 우선 사용과 문자열 fallback 처리 순서가 잘 정리되어 있습니다.

.github/workflows/prod-ci-cd.yml (1)

48-52: LGTM!

Supabase public key를 빌드 환경 변수로 추가하는 것이 적절합니다. Supabase anon/public key는 클라이언트 측에 노출되어도 안전합니다.

prod-ci-cd.yml이 유일한 프로덕션 빌드 워크플로우이므로 다른 워크플로우(publish-storybook.yml은 Chromatic 실행만 수행)에서는 이 환경 변수가 필요하지 않습니다.

.storybook/preview.ts (3)

2-9: LGTM! MSW Storybook 애드온 통합이 올바르게 구현되었습니다.

initialize()를 모듈 로드 시점에 호출하고 mswLoader를 전역으로 노출하는 패턴이 msw-storybook-addon의 권장 사용법과 일치합니다.


13-13: Chromatic 스냅샷 기본 비활성화 설정 확인.

기본적으로 스냅샷을 비활성화하고 필요한 스토리에서만 활성화하는 접근 방식은 비용 최적화 측면에서 좋습니다. 다만, 중요한 스토리들에 chromatic: { disableSnapshot: false } 설정이 누락되지 않았는지 확인이 필요합니다.


33-34: 전역 MSW 로더 설정 완료.

loaders 배열에 mswLoader를 추가하여 모든 스토리에서 MSW 핸들러를 사용할 수 있게 됩니다. 이는 PR 내 다른 스토리 파일들(SelectGroupPage.stories.tsx, ExpenseTimeHeader stories 등)의 MSW 기반 API 모킹과 잘 연동됩니다.

.github/workflows/publish-storybook.yml (1)

41-60: 스냅샷 변경 여부에 따른 조건부 PR 코멘트 분리가 잘 구현되었습니다.

changeCount 출력값을 활용한 조건부 메시지 분기는 명확한 피드백을 제공합니다. 스토리북 URL과 스냅샷 변경 정보를 구분하여 표시하는 것이 가독성에 좋습니다.

src/shared/ui/Text/index.tsx (1)

12-12: LGTM! style prop 추가가 깔끔하게 구현되었습니다.

기존 prop 패턴(className, textAlign 등)을 따르며, 인라인 스타일 오버라이드 기능을 제공합니다. 기존 API와의 하위 호환성이 유지됩니다. Based on learnings, Text 컴포넌트의 default export가 유지되어 기존 import 구문이 정상 작동합니다.

Also applies to: 22-22, 31-31

src/pages/home/ui/HomeExpenseItem/index.tsx (1)

47-54: LGTM!

Flex 컴포넌트를 활용한 레이아웃 변경이 적절합니다. Text 컴포넌트의 색상 토큰 사용이 디자인 시스템과 일관성을 유지하고 있습니다.

src/pages/billDetail/ui/ExpenseMemberItem/index.tsx (1)

11-11: LGTM!

MemberProfileImage 공유 컴포넌트로의 리팩토링이 적절합니다. 코드 재사용성이 향상되고 프로필 이미지 렌더링의 일관성이 유지됩니다.

Also applies to: 68-68

src/shared/ui/MemberProfile/index.tsx (1)

7-13: 인터페이스 설계가 깔끔합니다.

개별 props로 분리하여 컴포넌트의 재사용성과 유연성이 향상되었습니다. canDelete 옵셔널 prop과 기본값 설정도 적절합니다.

src/features/expense-management/ui/MemberExpenses/index.tsx (2)

17-23: 삭제 핸들러의 데이터 흐름 불일치를 확인해 주세요.

MemberProfilehandleDeleteButtonClick(id: number) => void 시그니처를 가지지만, 여기서는 id를 무시하고 member.name으로 삭제를 수행합니다. 의도된 설계라면 괜찮지만, 일관성 측면에서 확인이 필요합니다.

onDelete 인터페이스가 name 기반인 이유가 있다면 현재 구현이 맞습니다. 다만 id 기반 삭제로 통일하는 것이 더 안전할 수 있습니다 (이름 중복 가능성).


11-34: 리팩토링이 잘 되었습니다.

인라인 프로필 렌더링을 MemberProfile 컴포넌트로 교체하여 코드 중복이 줄고 유지보수성이 향상되었습니다.

src/pages/memberSetup/ui/AddMember/index.tsx (2)

83-98: 폼 구조가 간결해졌습니다.

불필요한 중첩 Flex 래퍼를 제거하고 InputGroup으로 직접 감싸는 방식으로 개선되었습니다.


108-118: MemberProfile 통합이 적절합니다.

canDelete={member.role !== 'MANAGER'} 로직이 삭제 API 에러 핸들링(Line 29-35)과 일관성을 유지합니다. 개별 props 전달 방식이 컴포넌트의 새 API와 정확히 일치합니다.

src/pages/memberSetup/ui/AddMember/index.stories.tsx (1)

33-35: Chromatic 스냅샷 설정이 적절합니다.

다른 스토리 파일들과 일관되게 disableSnapshot: false 설정이 추가되었습니다.

src/pages/login/LoginEntranceView.tsx (2)

1-7: LGTM! import 구조가 깔끔합니다.

필요한 모든 의존성이 올바르게 import되어 있으며, 스타일은 namespace import(* as S)로 적절히 처리되었습니다.


9-34: LGTM! 컴포넌트 구현이 적절합니다.

  • useTheme 훅을 통한 테마 접근이 올바르게 되어 있습니다.
  • gap={16}은 Flex 컴포넌트의 spacing props 가이드라인에 따라 raw number 사용이 적절합니다.
  • UI 구조가 명확하고 가독성이 좋습니다.

Based on learnings, Flex 컴포넌트의 spacing props에는 raw number를 전달하는 것이 올바른 사용법입니다.

src/pages/billDetail/ui/ExpenseTimeHeader/index.style.ts (1)

57-62: Timer 컴포넌트 추가 확인.

Grid 레이아웃으로 타이머 UI를 구성하는 접근 방식이 적절합니다. 다만, width: 174px가 하드코딩되어 있어 다양한 화면 크기에서 의도대로 동작하는지 확인이 필요할 수 있습니다.

타이머 숫자/구분자 5개 컬럼 배치(예: HH : MM : SS)에 대해 현재 구현이 의도한 대로 작동한다면 문제없습니다.

src/pages/login/LoginEntranceView.stories.tsx (1)

1-15: LGTM! Storybook 스토리 구조가 적절합니다.

  • Meta 구성이 프로젝트 컨벤션과 일치합니다.
  • chromatic: { disableSnapshot: false } 설정으로 스냅샷 테스트가 활성화되어 있습니다.
  • LoginEntranceView 컴포넌트가 props를 받지 않으므로 빈 Default 스토리가 적절합니다.
src/pages/home/HomePage.stories.tsx (1)

1-27: LGTM! 라우터 데코레이터가 적절하게 구성되었습니다.

  • createMemoryRouterRouterProvider를 사용한 라우팅 컨텍스트 제공이 올바릅니다.
  • HomePage가 라우팅 훅을 사용하는 경우 이 데코레이터가 필수적입니다.
  • Chromatic 스냅샷 설정과 스토리 구조가 프로젝트 컨벤션과 일치합니다.
src/pages/login/LoginEntranceView.styles.tsx (3)

3-10: ImgContainer 스타일 확인.

고정 높이 330px 사용이 다양한 화면 크기에서 의도한 레이아웃을 유지하는지 확인해주세요. 필요 시 상대적 단위나 미디어 쿼리 적용을 고려할 수 있습니다.


12-19: LGTM! EntranceImg 반응형 처리가 적절합니다.

width: 60%max-width: 17.25rem 조합으로 반응형 이미지 크기 조절이 잘 구현되어 있습니다.


21-26: LogoImg 중복 선언은 의도적입니다.

LoginPage.styles.tsLogoImg와 동일한 스타일이지만, 상태에 따라 분기되는 독립적인 UI에서 사용되며 향후 각각 다르게 변경될 가능성을 고려하여 의도적으로 중복 선언되었습니다.

Based on learnings, 이러한 경우 공통 모듈 추출보다 중복 유지가 더 적절한 설계입니다.

src/pages/login/LoginPage.tsx (1)

11-12: LGTM! 컴포넌트 분리가 잘 되었습니다.

LoginEntranceView를 별도 컴포넌트로 추출하여 LoginPage의 책임을 명확히 분리했습니다. 코드 가독성과 유지보수성이 향상되었습니다.

Also applies to: 37-38

src/pages/selectGroup/SelectGroupPage.tsx (1)

46-50: LGTM! 레이아웃 및 스타일 조정이 적절합니다.

시맨틱 컬러 토큰 사용과 디자인 시스템 기준의 spacing 값 적용이 잘 되었습니다. gap={8}, mx={20}, mt={20} 등 숫자 값 사용이 가이드라인과 일치합니다. Based on learnings, plain numbers for Flex spacing props.

Also applies to: 52-52

src/pages/home/HomePage.tsx (3)

71-74: LGTM! spacing 값들이 가이드라인에 맞게 적용되었습니다.

gap={16}, margin={20}, px={20}, py={18} 등 Flex spacing props에 plain numbers가 올바르게 사용되었습니다. Based on learnings.

Also applies to: 82-84


94-102: 인라인 스타일 사용이 적절합니다.

Text 컴포넌트에 새로 추가된 style prop을 활용하여 marginTop을 적용했습니다. 해당 스타일이 이 위치에서만 필요한 일회성 스타일이라면 적절한 접근입니다.


120-147: 레이아웃 구조 개선이 잘 되었습니다.

정산 헤더 영역의 레이아웃이 Flex 기반으로 정리되었고, spacing 값들이 일관되게 적용되었습니다.

src/pages/billDetail/ui/ExpenseTimeHeader/index.tsx (3)

175-183: LGTM! 복사 버튼 영역이 깔끔하게 정리되었습니다.

gap={4}와 아이콘 크기 축소(16x16)로 UI가 개선되었습니다.


214-214: Flex spacing props가 가이드라인에 맞게 적용되었습니다.

px={20}, gap={12} 등 plain numbers 사용이 올바릅니다. Based on learnings.


219-245: 타이머 UI 리팩토링이 잘 되었습니다.

배열 매핑을 통한 타이머 렌더링으로 코드 중복이 줄었습니다. 정적 배열에서 index를 key로 사용하는 것은 이 경우 적절합니다.

gridColumn: idx * 2 + 1 계산이 S.Timer의 5열 그리드 레이아웃(grid-template-columns: repeat(5, auto))과 정확히 일치합니다. 시간값이 1, 3, 5열에 자동 배치되고, 라벨도 동일한 열에 배치되어 의도한 레이아웃이 올바르게 구현되었습니다.

src/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsx (1)

18-53: MSW 핸들러 및 데코레이터 구성이 적절합니다.

Chromatic 스냅샷 설정, MSW 핸들러를 통한 API 모킹, 그리고 MemoryRouter와 QueryClientProvider를 사용한 데코레이터 구성이 잘 되어 있습니다.

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: 0

🧹 Nitpick comments (2)
src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx (1)

15-21: 핸들러에 Storybook action 사용 고려

현재 onClosesetBankName이 빈 함수로 되어 있어 Storybook UI에서 호출 여부를 확인할 수 없습니다. @storybook/addon-actionsaction 함수를 사용하면 Actions 패널에서 핸들러 호출을 추적할 수 있어 디버깅에 유용합니다.

♻️ 제안된 개선 사항
-import { Meta, StoryObj } from '@storybook/react';
+import { Meta, StoryObj } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
 import BankNameDrawer from './index';

 const meta: Meta<typeof BankNameDrawer> = {
   title: 'ui/BankNameDrawer',
   component: BankNameDrawer,
   parameters: {
     chromatic: { disableSnapshot: false },
   },
+  argTypes: {
+    onClose: { action: 'onClose' },
+    setBankName: { action: 'setBankName' },
+  },
 };

 export default meta;
 type Story = StoryObj<typeof BankNameDrawer>;

 export const Default: Story = {
   args: {
     open: true,
-    onClose: () => {},
-    setBankName: () => {},
   },
 };
src/shared/api/axios.ts (1)

30-36: URL 매칭 로직 개선 고려

현재 split('?')[0].endsWith('user/guest/token') 방식은 동작하지만, 향후 유사한 경로가 추가될 경우 의도치 않은 매칭이 발생할 수 있습니다. 좀 더 명확한 매칭을 위해 정규식이나 상수 정의를 고려해보세요.

♻️ 선택적 개선안
+const SUPABASE_AUTH_ENDPOINTS = ['user/guest/token'];
+
+const isSupabaseAuthEndpoint = (url?: string): boolean => {
+  if (!url) return false;
+  const path = url.split('?')[0];
+  return SUPABASE_AUTH_ENDPOINTS.some((endpoint) => path.endsWith(endpoint));
+};
+
 // SUPABASE용 apikey 헤더 추가 (필요 시)
-else if (newConfig.url?.split('?')[0].endsWith('user/guest/token')) {
+else if (isSupabaseAuthEndpoint(newConfig.url)) {
   newConfig.headers = AxiosHeaders.from({
     ...newConfig.headers,
     apikey: import.meta.env.VITE_SUPABASE_PUBLIC_KEY,
   });
 }

또한 else if 구조로 인해 useMock: true인 경우 apikey 헤더가 추가되지 않습니다. mock 환경에서 실제 인증이 필요 없다면 현재 로직이 맞지만, 의도된 동작인지 확인 부탁드립니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 544e48f and cdc0d2f.

📒 Files selected for processing (3)
  • src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx
  • src/shared/api/axios.ts
  • src/shared/ui/MemberProfile/index.stories.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/shared/ui/MemberProfile/index.stories.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-28T04:24:56.857Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 12
File: src/pages/login/LoginEntranceView.tsx:13-20
Timestamp: 2025-12-28T04:24:56.857Z
Learning: Guideline: Do not pass raw numbers for spacing props (e.g., gap, padding, margin) to the Flex component. If the design system uses tokenized spacing (theme.unit[...] ), derive spacing from design tokens instead of hard-coded numbers only if the component expects a direct number. In this case, the correct usage is to pass plain numbers (e.g., 16) directly for spacing props used by Flex, rather than theme.unit[16]. Apply this consistently across TSX files where Flex spacing props are used. This improves readability and ensures consistent spacing values align with design tokens.

Applied to files:

  • src/shared/api/axios.ts
  • src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx
🧬 Code graph analysis (1)
src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx (5)
src/pages/memberSetup/ui/AddMember/index.stories.tsx (1)
  • Default (49-54)
src/pages/home/HomePage.stories.tsx (1)
  • Default (27-27)
src/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsx (1)
  • Default (58-71)
src/pages/login/LoginEntranceView.stories.tsx (1)
  • Default (15-15)
src/pages/selectGroup/SelectGroupPage.stories.tsx (1)
  • Default (57-57)
🔇 Additional comments (2)
src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsx (1)

1-13: LGTM!

Storybook 메타 설정과 타입 정의가 프로젝트의 다른 스토리 파일과 일관된 패턴을 따르고 있습니다.

src/shared/api/axios.ts (1)

5-5: LGTM!

Supabase Edge Functions 엔드포인트(/functions/v1)로 baseURL 변경이 적절합니다.

@yoouyeon yoouyeon changed the title Storybook MSW 애드온 통합 및 컴포넌트 분리 Flex 컴포넌트 버그 수정과 프로필 컴포넌트 추가, 그리고 supabase 연결 Jan 13, 2026
@yoouyeon yoouyeon merged commit 0fbcf29 into develop Jan 13, 2026
1 of 2 checks passed
@yoouyeon yoouyeon mentioned this pull request Jan 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎨 Design 디자인 관련 작업 🔥 Hotfix 긴급 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants