-
Notifications
You must be signed in to change notification settings - Fork 0
Flex 컴포넌트 버그 수정과 프로필 컴포넌트 추가, 그리고 supabase 연결 #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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)
📝 WalkthroughWalkthrough이 PR은 MSW Storybook 애드온을 통합하고 MemberProfile 컴포넌트의 props를 분리·리팩토링하며, 여러 페이지의 레이아웃/스타일 값을 조정합니다. CI/CD에 VITE_SUPABASE_PUBLIC_KEY를 추가하고 axios의 baseURL을 functions/v1로 변경하며 다수의 Storybook 스토리 파일을 추가합니다. Changes
Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 1✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this 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:sizeprop이 필수인데 fallback이 있습니다.
sizeprop은 인터페이스에서 필수(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:keyprop이 불필요합니다.
keyprop은 리스트 렌더링 시 부모 컴포넌트에서 사용됩니다.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:ptprop의 타입을 확인하세요.
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
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (34)
.github/workflows/prod-ci-cd.yml.github/workflows/publish-storybook.yml.storybook/preview.tspackage.jsonsrc/features/expense-management/ui/MemberExpenses/index.styles.tssrc/features/expense-management/ui/MemberExpenses/index.tsxsrc/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsxsrc/pages/addAccountStep/ui/BankNameDrawer/index.tsxsrc/pages/billDetail/ui/CurvedProgressBar/index.style.tssrc/pages/billDetail/ui/ExpenseMemberItem/index.style.tssrc/pages/billDetail/ui/ExpenseMemberItem/index.tsxsrc/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsxsrc/pages/billDetail/ui/ExpenseTimeHeader/index.style.tssrc/pages/billDetail/ui/ExpenseTimeHeader/index.tsxsrc/pages/home/HomePage.stories.tsxsrc/pages/home/HomePage.tsxsrc/pages/home/ui/HomeExpenseItem/index.tsxsrc/pages/login/LoginEntranceView.stories.tsxsrc/pages/login/LoginEntranceView.styles.tsxsrc/pages/login/LoginEntranceView.tsxsrc/pages/login/LoginPage.styles.tssrc/pages/login/LoginPage.tsxsrc/pages/memberSetup/ui/AddMember/index.stories.tsxsrc/pages/memberSetup/ui/AddMember/index.tsxsrc/pages/selectGroup/SelectGroupPage.stories.tsxsrc/pages/selectGroup/SelectGroupPage.tsxsrc/shared/api/axios.tssrc/shared/ui/Flex/index.tsxsrc/shared/ui/MemberProfile/index.stories.tssrc/shared/ui/MemberProfile/index.tsxsrc/shared/ui/MemberProfileImage/index.stories.tssrc/shared/ui/MemberProfileImage/index.styles.tssrc/shared/ui/MemberProfileImage/index.tsxsrc/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.tsxsrc/pages/billDetail/ui/CurvedProgressBar/index.style.tssrc/shared/ui/MemberProfileImage/index.stories.tssrc/shared/ui/Text/index.tsxsrc/pages/login/LoginEntranceView.styles.tsxsrc/pages/memberSetup/ui/AddMember/index.stories.tsxsrc/pages/home/HomePage.stories.tsxsrc/shared/ui/Flex/index.tsxsrc/pages/billDetail/ui/ExpenseTimeHeader/index.stories.tsxsrc/shared/ui/MemberProfile/index.stories.tssrc/pages/login/LoginEntranceView.stories.tsxsrc/pages/login/LoginEntranceView.tsxsrc/pages/addAccountStep/ui/BankNameDrawer/index.tsxsrc/shared/api/axios.tssrc/pages/billDetail/ui/ExpenseMemberItem/index.tsxsrc/pages/selectGroup/SelectGroupPage.stories.tsxsrc/pages/billDetail/ui/ExpenseTimeHeader/index.tsxsrc/shared/ui/MemberProfileImage/index.styles.tssrc/pages/home/ui/HomeExpenseItem/index.tsxsrc/shared/ui/MemberProfileImage/index.tsxsrc/pages/home/HomePage.tsxsrc/pages/selectGroup/SelectGroupPage.tsxsrc/features/expense-management/ui/MemberExpenses/index.tsxsrc/pages/login/LoginPage.tsxsrc/pages/billDetail/ui/ExpenseTimeHeader/index.style.tssrc/pages/memberSetup/ui/AddMember/index.tsxsrc/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.tsxsrc/pages/login/LoginEntranceView.tsxsrc/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.tsxsrc/pages/login/LoginEntranceView.tsxsrc/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.tsxsrc/pages/login/LoginEntranceView.tsxsrc/shared/ui/MemberProfileImage/index.styles.tssrc/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.tsxsrc/pages/home/ui/HomeExpenseItem/index.tsxsrc/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.tsxsrc/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 컴포넌트의
pxprop에 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!styleprop 추가가 깔끔하게 구현되었습니다.기존 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: 삭제 핸들러의 데이터 흐름 불일치를 확인해 주세요.
MemberProfile의handleDeleteButtonClick은(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! 라우터 데코레이터가 적절하게 구성되었습니다.
createMemoryRouter와RouterProvider를 사용한 라우팅 컨텍스트 제공이 올바릅니다.- 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.ts의LogoImg와 동일한 스타일이지만, 상태에 따라 분기되는 독립적인 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컴포넌트에 새로 추가된styleprop을 활용하여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를 사용한 데코레이터 구성이 잘 되어 있습니다.
There was a problem hiding this 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: 핸들러에 Storybookaction사용 고려현재
onClose와setBankName이 빈 함수로 되어 있어 Storybook UI에서 호출 여부를 확인할 수 없습니다.@storybook/addon-actions의action함수를 사용하면 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
📒 Files selected for processing (3)
src/pages/addAccountStep/ui/BankNameDrawer/index.stories.tsxsrc/shared/api/axios.tssrc/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.tssrc/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 변경이 적절합니다.
hotfix 변경사항 동기화
Summary by CodeRabbit
릴리스 노트
새로운 기능
스타일
리팩터링
Chores
✏️ Tip: You can customize this high-level summary in your review settings.