-
Notifications
You must be signed in to change notification settings - Fork 0
경로·모듈 구조 재구성 및 라우터·설정 갱신 제목 #7
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
Co-authored-by: 심여은 <[email protected]>
Co-authored-by: 심여은 <[email protected]>
group setup 페이지 스타일 import 해결 Co-authored-by: 심여은 <[email protected]>
ShareButton 컴포넌트를 shared로 이동 Co-authored-by: 심여은 <[email protected]>
funnel import 정리 Co-authored-by: 심여은 <[email protected]>
Co-authored-by: 심여은 <[email protected]>
Co-authored-by: 심여은 <[email protected]>
utils를 lib로 변경 Co-authored-by: 심여은 <[email protected]>
Co-authored-by: 심여은 <[email protected]>
완전한 전환 후에 사용하기 위해서 우선 주석처리해둠 Co-authored-by: 심여은 <[email protected]>
settlement, expense, group이 남아 있다. Co-authored-by: 심여은 <[email protected]>
Co-authored-by: 심여은 <[email protected]>
Co-authored-by: 심여은 <[email protected]>
Co-authored-by: 심여은 <[email protected]>
Co-authored-by: 심여은 <[email protected]>
Walkthrough프로젝트 폴더와 모듈 경로를 대규모로 재조정했습니다. 공용 자원은 Changes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (1 passed)
✨ Finishing touches🧪 Generate unit tests
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
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 |
|
✨ Storybook preview : https://6867dc9b2bbd1b3d76b92f4a-smjvgmfmji.chromatic.com/ |
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: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (17)
src/pages/editExpenseStep/EditExpenseStepPage.tsx (1)
35-43: 404 응답일 때 onNext 호출을 onBack(또는 목록 이동)으로 변경지출 항목이 없는데 다음 단계로 넘어가면 맥락이 깨집니다. 뒤로 가기 또는 지출 목록 화면으로 분기하세요.
파일: src/pages/editExpenseStep/EditExpenseStepPage.tsx (약 35–41행)- onNext(); + onBack();package.json (1)
11-11: SVGR 출력 경로(src/assets/svgs)와 코드의 import('@/shared/assets/svgs/icon') 불일치 — 즉시 수정 필요프로젝트 전반에서 '@/shared/assets/svgs/icon'을 사용(.storybook/stories/icon-gallery.mdx, 다수의 src/pages/* 등)하는데 package.json의 svgr 스크립트는 'src/assets/svgs'로 출력되어 빌드/런타임 오류 위험이 있습니다. 조치 (택1):
옵션 A) 권장 — svgr 출력 경로를 shared로 변경:
- "svgr": "npx @svgr/cli -d src/assets/svgs --typescript --no-dimensions public/svgs", + "svgr": "npx @svgr/cli -d src/shared/assets/svgs --typescript --no-dimensions public/svgs",옵션 B) 또는 스토리/코드의 import 경로를 기존 assets로 되돌리기(모든 '@/shared/assets/svgs/icon' 참조를 '@/assets/svgs/icon'로 변경).
.eslintrc.cjs (1)
11-16: .eslintrc.cjs — '@feature-sliced' extends 활성화
devDeps에 '@feature-sliced/eslint-config'가 있으니 .eslintrc.cjs(약 11–16행)에서 extends 배열의 주석을 해제하고 '@feature-sliced'를 추가하세요 (extends에서는 'eslint-config' 접미사 생략).src/pages/groupNameSetup/GroupNameSetupPage.tsx (2)
15-19: 공백만 입력 통과 가능 — 스키마에 trim 추가 필요현재
.min(1)은 공백 1자도 유효로 처리됩니다..trim()을 추가해 공백 제거 후 최소 길이를 검증하세요.적용 diff:
- groupName: z - .string() - .min(1, { message: '모임 이름을 1글자 이상 입력해주세요.' }), + groupName: z + .string() + .trim() + .min(1, { message: '모임 이름을 1글자 이상 입력해주세요.' }),
2-2: useNavigate 등 react-router import 경로 오류 — 즉시 수정 필요useNavigate는 react-router-dom에서 import해야 합니다. 현재 해당 파일뿐만 아니라 레포 전역에서 'react-router'로 잘못 import한 사례가 다수 발견되어 런타임 크래시 가능성이 있습니다.
- 수정 (파일: src/pages/groupNameSetup/GroupNameSetupPage.tsx) — 적용 diff:
-import { useNavigate } from 'react-router'; +import { useNavigate } from 'react-router-dom';
- 레포 전역 영향 예시(수정 필요): src/pages/shareStep/ShareStepPage.tsx, src/pages/selectGroup/SelectGroupPage.tsx, src/pages/passwordSetup/PasswordSetupPage.tsx, src/pages/onboarding/OnboardingPage.tsx, src/pages/login/LoginPage.tsx, src/pages/memberSetup/MemberSetupPage.tsx, src/pages/home/HomePage.tsx, src/pages/createExpenseStep/CreateExpenseStepPage.tsx, src/features/character-management/ui/CharacterBottomSheet/index.tsx, src/entities/auth/api/useGetGuestToken.ts 등. 모든 'from "react-router"' import를 점검해 DOM 전용 훅/컴포넌트는 'react-router-dom'로 변경하십시오.
src/features/expense-management/ui/MemberExpenses/index.styles.ts (1)
40-44: styled-components 템플릿 내 ‘//’ 주석은 미지원일 수 있습니다 → CSS 주석으로 교체 권장Stylis(v4) 기준
//주석은 파싱 오류/무시 이슈가 있어 안전하지 않습니다./* ... */로 변경해 주세요.적용 diff:
- position: relative; // 부모 요소 + position: relative; /* 부모 요소 */- position: absolute; // 자식 요소 + position: absolute; /* 자식 요소 */Also applies to: 46-50
src/main.tsx (1)
5-8: enableMocking 실패 시 렌더링 불가 가능성 — finally에서 렌더링 보장 권장mock 초기화가 실패하면 앱이 아예 렌더링되지 않을 수 있습니다. 실패 시에도 렌더링을 보장하도록 수정해 주세요.
-enableMocking().then(() => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - createRoot(document.getElementById('root')!).render(<App />); -}); +enableMocking() + .catch((e) => { + // eslint-disable-next-line no-console + console.error('[mocks] enableMocking 실패 — mocks 없이 렌더링합니다.', e); + }) + .finally(() => { + const rootEl = document.getElementById('root'); + if (!rootEl) throw new Error('#root 엘리먼트를 찾을 수 없습니다.'); + createRoot(rootEl).render(<App />); + });src/entities/expense/model/expense.type.ts (1)
61-68: 날짜 타입 불일치:ExpenseDetail.date는Date, 다른 곳은stringAPI 경계에서 직렬화/역직렬화 혼선을 유발합니다. 일관되게
ISO문자열로 유지하거나, 모든 수신 지점에서 파싱을 보장하세요. 간단히 문자열로 통일하는 패치 제안합니다.export interface ExpenseDetail { id: number; - date: Date; + date: string; // ISO-8601 (YYYY-MM-DD 또는 YYYY-MM-DDTHH:mm:ssZ) content: string; totalAmount: number; groupMembers: string[]; }src/pages/error/ErrorPage.tsx (1)
37-39: 중첩 인터랙티브 요소(button 안에 a) — 접근성/이벤트 문제button 내부에 a를 넣는 패턴은 HTML 상 비권장/비유효이며 보조공학, 포커스, 이벤트 버블링 문제가 납니다. 단일 인터랙티브 요소로 렌더링해 주세요.
- <Button onClick={action.onClick}> - <a href={action.href}>{action.text}</a> - </Button> + {action.href ? ( + <Button as="a" href={action.href} onClick={action.onClick}> + {action.text} + </Button> + ) : ( + <Button onClick={action.onClick}>{action.text}</Button> + )}비고:
Button이as/href를 지원하지 않으면, 앵커 자체를 스타일링한 컴포넌트(예:styled.a)로 대체해 주세요.src/features/expense-management/ui/NumPad/index.tsx (1)
30-41: 리스트 key에 난수 사용 — 재조정 비효율 및 불필요한 re-mount 유발
nanoid()는 매 렌더마다 바뀌므로 Diff 효율이 떨어지고 포커스/애니메이션 깨짐을 유발할 수 있습니다. 불변 키(라벨/아이디/인덱스)를 사용하세요.- key={nanoid(10)} + key={shortcut.label}- key={nanoid(10)} + key={cell.label}라벨이 중복 가능성이 있으면, 컨트롤러 데이터에
id를 추가해 그 값을 key로 사용해 주세요.Also applies to: 44-57
src/features/expense-management/api/usePutUpdateAccount.ts (1)
10-14: 날짜 필드 타입 검토: 서버 응답이 문자열이면 Date 사용은 부정확
createdAt/expiredAt가 서버에서 ISO 문자열로 오면 현재 타입은 잘못된 신뢰를 줍니다. 문자열로 맞추거나, 받아서 파싱하는 계층을 명시하세요. 우선 문자열로 통일 제안합니다.interface AccountData { id: number; writer: number; - createdAt: Date; - expiredAt: Date; + createdAt: string; // ISO-8601 + expiredAt: string; // ISO-8601 bank: string; accountNumber: string; }src/entities/expense/api/expense.ts (1)
45-48: 버그: 상세 조회 엔드포인트에 선행 슬래시 누락
getDetail만expenses/details앞에/가 없어 baseURL 조합에 따라 404가 날 수 있습니다. 다른 메서드와 동일하게 수정이 필요합니다.- .get(`expenses/details?groupToken=${groupToken}`) + .get(`/expenses/details?groupToken=${groupToken}`) .then((res) => res.data),src/mocks/handlers/group.ts (1)
25-31: GET 응답 상태 코드가 200으로 반환되는 버그현재 401을 본문에만 담고 실제 HTTP status는 200입니다. MSW
HttpResponse.json두 번째 인자로 status를 넘겨야 합니다.- if (!groupToken) { - return HttpResponse.json({ - error: 'groupToken is required', - status: 401, - }); - } + if (!groupToken) { + return HttpResponse.json( + { error: 'groupToken is required' }, + { status: 401 } + ); + }(참고: 파라미터 누락은 400이 더 적절할 수 있으나, 우선 의도한 401을 실제 status로 반영했습니다.)
Also applies to: 32-35
src/pages/billDetail/ui/ExpenseMemberItem/index.tsx (1)
30-34: 변이 변수 stale 및await mutate()오사용 가능성훅 초기화 시점의
isPaid가 캡처되어 최신 상태가 서버로 안 나갈 수 있고(상태 변경 후에도),await mutate()는 표준 React Query에서 Promise를 반환하지 않습니다.mutateAsync(variables)로 최신 값을 넘겨 호출하세요.다음 수정안을 권장합니다.
- const updatePaymentStatusMutation = useUpdatePaymentStatus({ - groupToken, - groupMemberId: member.id, - isPaid, - }); + const updatePaymentStatusMutation = useUpdatePaymentStatus(); /** confim 버튼 클릭 시 api를 호출하는 함수 */ const handleChangeButtonSubmit = async () => { - await updatePaymentStatusMutation.mutate(); + await updatePaymentStatusMutation.mutateAsync({ + groupToken, + groupMemberId: member.id, + isPaid, + }); setIsConfirm(false); setOpen(false); };만약 훅 시그니처가 고정 변수 패턴이라면, 훅 내부를
mutationFn: (v) => updatePaymentStatus(v)형태로 바꾸고 외부에선 항상mutateAsync(variables)로 넘기는 방향을 권장합니다.Also applies to: 49-53
src/pages/addAccountStep/AddAccountStepPage.tsx (1)
87-92: 계좌번호 input type=number 사용은 선행 0 손실 위험 (중대)은행 계좌는 숫자열이지만 수치가 아닙니다. number 타입은 선행 0 삭제/스크롤 입력/브라우저별 표기 문제를 유발합니다. text + numeric 키패드로 교체하고 비숫자 제거를 권장합니다.
적용 diff:
- <Input + <Input placeholder="계좌번호를 입력해주세요." value={accountNumber} - onChange={(e) => setAccountNumber(e.target.value)} - type="number" - inputMode="numeric" + onChange={(e) => + setAccountNumber(e.target.value.replace(/\D/g, '')) + } + type="text" + inputMode="numeric" + pattern="[0-9]*" />src/features/expense-management/ui/FormCard/index.tsx (2)
37-45: amount의 falsy 체크로 0원 케이스 누락 + remainderData 정리 누락
- if (!amount ...)는 0을 falsy로 취급해 분배 로직이 건너뛰어집니다.
- member가 없거나 amount를 비우면 기존 remainder 알림이 남아 있을 수 있습니다.
아래처럼 null/undefined만 걸러주고, 조기 반환 시 remainderData도 초기화하세요. 또한 수치 연산 전 Number 캐스팅을 권장합니다.
- if (!amount || !memberExpenses) return; + if (amount == null || !Array.isArray(memberExpenses)) { + setRemainderData(null); + return; + } if (amount && memberExpenses && memberExpenses.length > 0) { - const { distributeResult, remainder } = distributeAmount( - amount, + const amountNum = Number(amount); + if (!Number.isFinite(amountNum)) { + setRemainderData(null); + return; + } + const { distributeResult, remainder } = distributeAmount( + amountNum, memberExpenses.length );
114-119: 날짜 파싱/표현의 타임존 이슈 가능성 (iOS/Safari에서 하루 밀림)'yyyy-MM-dd' 문자열을 new Date(...)로 만들면 환경에 따라 UTC 해석되어 하루가 밀릴 수 있습니다. parseISO + isValid로 안전 파싱하고, selected는 null 허용이 더 안전합니다.
-import { format } from 'date-fns'; +import { format, parseISO, isValid } from 'date-fns'; ... - <BillDatePicker - selected={new Date(field.value)} - onChange={(date) => - field.onChange(format(date || new Date(), 'yyyy-MM-dd')) - } - /> + <BillDatePicker + selected={ + field.value ? (isValid(parseISO(field.value)) ? parseISO(field.value) : null) : null + } + onChange={(date) => + field.onChange(format(date || new Date(), 'yyyy-MM-dd')) + } + />대안: 폼 상태를 Date 객체로 유지하고, 전송 직전에만 문자열로 포맷.
🧹 Nitpick comments (89)
src/pages/billDetail/ui/ExpenseTimeHeader/index.type.ts (2)
11-24: 타입 전용 파일에 런타임 값이 함께 존재 — 분리 또는 파일명 정리 제안
index.type.ts가 이미지 import와StatusContent런타임 상수까지 노출합니다. FSD와 명명 규칙을 고려하면 타입과 런타임 상수를 분리(index.type.ts↔index.const.ts)하거나 파일명을index.ts로 정리하는 편이 유지보수에 유리합니다.
11-24: 타이핑 강화: 키 누락/오타 방지 및 리터럴 보존
Record<StatusType, StatusInfo>는 좋지만,satisfies를 써서 키 누락/오타를 컴파일 타임에 더 강하게 잡을 수 있습니다. 또한 메시지 리터럴을 보존하려면as const를 함께 적용하세요(TS 4.9+).아래처럼 변경을 고려해 주세요:
export const StatusContent = { pending: { message: '마감 전에 정산하면, 귀여운 캐릭터가 뿅!', image: ModdoFace, }, success: { message: '정산, 완벽하게 클리어!', image: ModdoFace, }, failure: { message: '정산 마감 시간이 초과되었어요', image: CryingModdoFace, }, } as const satisfies Record<StatusType, StatusInfo>;src/pages/billDetail/ui/Tabs/index.tsx (2)
26-31: Tabs A11y 보강 제안: role/aria 및 키보드 액션 추가스크린리더/키보드 접근성 강화를 위해
role="tablist",role="tab",aria-selected,tabIndex, Enter/Space 핸들링을 권장합니다. (만약S.Tab이 실제 button이면type="button"도 추가)- <S.TabsList>{children}</S.TabsList> + <S.TabsList role="tablist">{children}</S.TabsList> @@ - <S.Tab $isActive={isActive} onClick={() => setActiveTab(value)}> + <S.Tab + $isActive={isActive} + role="tab" + aria-selected={isActive} + tabIndex={0} + onClick={() => setActiveTab(value)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') setActiveTab(value); + }} + >추가로 패널과 연동한다면
id/aria-controls연결도 고려해 주세요.Also applies to: 42-51
1-1: React 타입 import로 정리 (불필요한React.네임스페이스 제거)
React.ReactNode대신 타입 전용 import를 사용하면 깔끔합니다.-import { createContext, useContext, useMemo } from 'react'; +import { createContext, useContext, useMemo, type ReactNode } from 'react'; @@ - children: React.ReactNode; + children: ReactNode;Also applies to: 5-9
src/entities/auth/api/auth.ts (1)
11-13: Axios 제네릭으로 응답 타입 구체화응답 타입을 명시하여 추론 정확도를 높이세요.
- const response = await axiosInstance.get('/user/guest/token'); + const response = await axiosInstance.get<GuestTokenData>('/user/guest/token'); return response.data;src/entities/auth/api/useGetGuestToken.ts (3)
10-18: 네비게이션·스토리지 부수효과를 onSuccess로 이동부수효과는 queryFn보다 onSuccess에서 처리하는 것이 패턴에 맞고 취소/재시도와의 상호작용도 안전합니다.
return useQuery({ queryKey: ['guestToken'], - queryFn: async () => { - const response = await getGuestToken(); - if (response?.accessToken) { - localStorage.setItem('accessToken', `Bearer ${response?.accessToken}`); - navigate(ROUTE.selectGroup); - return response; - } - throw new Error('Access Token not found'); - }, + queryFn: getGuestToken, + onSuccess: (response) => { + if (!response?.accessToken) throw new Error('Access Token not found'); + localStorage.setItem('accessToken', response.accessToken); + navigate(ROUTE.selectGroup); + }, enabled: false, // refetch가 호출될 때만 실행되도록 설정 });
12-16: 로컬스토리지 토큰 저장은 XSS 위험가능하면 httpOnly+Secure 쿠키로 전환을 검토하세요. 최소한 SameSite, 짧은 만료, 정기 로테이션, XSS 방지(엄격 CSP 등)를 함께 적용하세요.
19-20: enabled:false 사용 의도 재확인해당 훅의 호출부에서 refetch 트리거를 보장하는지 확인 바랍니다. 액션 성격이라면 useMutation 전환도 고려해볼 만합니다.
src/entities/auth/lib/getGroupManagerAuth.ts (4)
5-21: SSR 고려: loader에서 localStorage 직접 접근은 서버 렌더링 시 크래시 위험현재 로더가 브라우저 전용 저장소(localStorage)에 바로 접근합니다. 앱이 CSR 전용이면 괜찮지만, SSR/프리렌더링이 개입되면
ReferenceError: localStorage is not defined가 납니다. 환경을 명확히 하고, SSR 가능성이 있으면 쿠키/헤더 기반으로 전환 또는 안전 가드(typeof window !== 'undefined')가 필요합니다.
6-7: 중복 로직 정리 제안(checkAuth와 겹침)
accessToken확인은checkAuth와 중복됩니다. 공통 유틸(예:requireAccessToken()), 그리고 이 파일만의requireGroupToken()로 분리해 두 로더에서 재사용하는 편이 깔끔합니다.예시:
+// src/shared/lib/authGuards.ts +export const requireAccessToken = () => { + const token = localStorage.getItem('accessToken'); + return token ?? null; +}; +export const requireGroupToken = () => { + const token = localStorage.getItem('groupToken'); + return token ?? null; +};그리고 본 로더:
-const accessToken = localStorage.getItem('accessToken'); +const accessToken = requireAccessToken(); if (!accessToken) return redirect(ROUTE.login); -const groupToken = localStorage.getItem('groupToken'); +const groupToken = requireGroupToken(); if (!groupToken) return redirect(ROUTE.selectGroup);Also applies to: 15-21
23-27: json 헬퍼 사용 고려(타이핑/헤더 확장 용이)단순 객체 반환도 동작하지만, 추후 캐시/헤더 제어를 위해
json({ accessToken, groupToken })사용을 권장합니다.-import { LoaderFunction, redirect } from 'react-router'; +import { LoaderFunction, redirect, json } from 'react-router'; - return { - accessToken, - groupToken, - }; + return json({ accessToken, groupToken });
1-1: react-router import 출처 재확인팀 내 다른 파일 일부가
react-router-dom에서redirect/json을 가져오는 경우가 있습니다. 프로젝트의 react-router 버전에 맞춰 import 출처를 통일해 주세요(타입은 core, 함수는 dom 등 혼재 시 트리셰이킹/타입 충돌 가능).src/entities/auth/lib/checkAuth.ts (4)
13-19: Loader 타입 명시로 일관성/오류 탐지 강화동일 디렉터리의
getGroupManagerAuth는LoaderFunction을 사용합니다. 이 함수도 로더로 쓰인다면 타입을 명시해 일관성과 TS 진단을 확보하세요.-import { redirect } from 'react-router'; +import { redirect, type LoaderFunction } from 'react-router'; - -const checkAuth = () => { +const checkAuth: LoaderFunction = async () => { const token = localStorage.getItem('accessToken'); if (!token) { return redirect(ROUTE.login); } - return null; + return null; };
14-17: SSR 환경 주의(localStorage 직접 접근)이 로더도 SSR이 개입되면 실패합니다. 앱 특성상 CSR 전용이 아니라면, 서버 판별 가드 또는 쿠키/세션 기반으로 전환이 필요합니다.
8-12: 토큰 저장 전략 전환 TODO — 이슈화 권장주석의 TODO대로 httpOnly 쿠키 기반 리프레시 플로우 전환은 보안상 중요(XSS 내성). 작은 범위 PR로 분리해 이슈/체크리스트로 관리하길 권장합니다. 필요하면 작업 계획 템플릿 드립니다.
14-17: 보안 메모: localStorage 액세스 토큰 노출 리스크현재 구조는 XSS 발생 시 토큰 탈취 가능성이 큽니다. 리프레시 토큰은 httpOnly 쿠키, 액세스 토큰은 메모리 보관 + 짧은 수명으로 설계하는 패턴을 추천합니다.
src/pages/billDetail/ui/CurvedProgressBar/index.tsx (3)
11-14: rest props가 타입상 {}로 수렴 → div 속성 전달(예: className, data-*, style) 불가. 확장 타입으로 래핑 권장.Wrapper로 전달하려는 의도라면 DOM 속성 전달을 허용하도록 prop 타입을 확장하세요.
다음과 같이 수정 제안드립니다:
-import { ReactNode } from 'react'; +import type { ReactNode, ComponentPropsWithoutRef } from 'react'; @@ -interface CurvedProgressBarProps { - percentage: number; - children?: ReactNode; -} +type CurvedProgressBarProps = { + percentage: number; + children?: ReactNode; +} & ComponentPropsWithoutRef<'div'>;Also applies to: 16-16, 24-24
12-12: percentage 유효 범위 클램프(0~100) 권장.가드가 없으면 외부 입력에 따라 라이브러리 경고/UI 깨짐 가능성이 있습니다.
아래와 같이 안전하게 클램프하세요.
function CurvedProgressBar({ percentage, children, ...rest }: CurvedProgressBarProps) { const theme = useTheme(); return ( <S.Wrapper {...rest}> - <ChangingProgressProvider values={[0, percentage]}> + {(() => { + const clamped = Math.max(0, Math.min(percentage, 100)); + return ( + <ChangingProgressProvider values={[0, clamped]}> {(value) => ( <CircularProgressbarWithChildren value={value} circleRatio={0.5} strokeWidth={10.5} styles={buildStyles({ rotation: 1 / 2 + 1 / 4, strokeLinecap: 'round', trailColor: `${theme.color.semantic.secondary.strong}`, pathColor: `${theme.color.semantic.orange.default}`, })} > {children} </CircularProgressbarWithChildren> )} - </ChangingProgressProvider> + </ChangingProgressProvider> + ); + })()} </S.Wrapper> ); }Also applies to: 25-36
31-36: (선택) styles 객체 메모이즈로 불필요한 재생성 최소화.빌드 스타일 객체가 매 렌더마다 새로 생성됩니다. 빈번히 렌더되는 경우 미세한 최적화 여지가 있습니다.
-import { - buildStyles, - CircularProgressbarWithChildren, -} from 'react-circular-progressbar'; +import { buildStyles, CircularProgressbarWithChildren } from 'react-circular-progressbar'; +import { useMemo } from 'react'; @@ - styles={buildStyles({ + styles={useMemo(() => buildStyles({ rotation: 1 / 2 + 1 / 4, strokeLinecap: 'round', trailColor: `${theme.color.semantic.secondary.strong}`, pathColor: `${theme.color.semantic.orange.default}`, - })} + }), [theme])}src/pages/editExpenseStep/EditExpenseStepPage.tsx (4)
1-1: useLoaderData import 경로 확인 (react-router-dom 권장)브라우저 앱이면 일반적으로
react-router-dom에서 가져옵니다. 번들/트리셰이킹 이슈 예방 차원에서 경로 확인 부탁드립니다.아래 변경이 맞는지 확인해 주세요:
-import { useLoaderData } from 'react-router'; +import { useLoaderData } from 'react-router-dom';
29-29: useLoaderData 반환 타입 명시로 안전성 향상loader 데이터 구조를 타입으로 고정하면 추후 리팩터 시 오류를 조기에 잡을 수 있습니다.
아래처럼 제네릭을 지정해 주세요:
-const { groupToken } = useLoaderData(); +const { groupToken } = useLoaderData<{ groupToken: string }>();
47-58: data.expenses[0] 접근 안전성 확보빈 배열일 경우
undefined를 API에 전송할 수 있습니다. 가드 추가를 권장합니다.다음과 같이 보강해 주세요:
-const updateHandler = handleSubmit((data) => - mutation.mutate( - { - groupToken, - data: data.expenses[0], - expenseId, - }, - { - onSuccess: onNext, - } - ) -); +const updateHandler = handleSubmit((data) => { + const first = data.expenses?.[0]; + if (!first) { + showToast({ type: 'error', content: '입력된 지출이 없어요.' }); + return; + } + mutation.mutate( + { groupToken, data: first, expenseId }, + { onSuccess: onNext } + ); +});
83-85: 불필요한 ref={null} 제거 제안
FormCard가forwardRef를 사용하지 않거나 ref가 필요 없다면 prop을 제거하세요. 의미 없는 prop 전달을 줄여 가독성과 타입 안전성을 높일 수 있습니다.-<FormCard key={field.id} ref={null} index={index} /> +<FormCard key={field.id} index={index} />package.json (1)
51-51: ESLint 의존성 추가만 있고 설정에서 미사용
@feature-sliced/eslint-config,eslint-plugin-boundaries를 devDeps에 추가했지만.eslintrc.cjs에서 활성화되지 않았습니다. 사용하지 않을 거라면 의존성을 제거하고, 사용할 거라면 extends/플러그인을 켜 주세요.다음 코멘트(.eslintrc.cjs)에 제안 diff를 남겼습니다. 의도에 맞는지 확인 부탁드립니다.
Also applies to: 77-79
.storybook/stories/units-radius.mdx (1)
29-37: 비수치 토큰에서 px 표기 'NaNpx' 가능성
parseFloat(value) * 16은'max','fit','auto'등 비수치 토큰에서NaNpx가 표시될 수 있습니다. 안전 가드를 권장합니다.- const pxValue = parseFloat(value) * 16; // rem → px 변환 + const num = Number.parseFloat(String(value)); + const pxValue = Number.isFinite(num) ? num * 16 : null; // rem → px 변환 ... - <td style={{ padding: theme.unit[12], color: theme.color.semantic.text.default }}>{pxValue}px</td> + <td style={{ padding: theme.unit[12], color: theme.color.semantic.text.default }}> + {pxValue !== null ? `${pxValue}px` : '-'} + </td>Also applies to: 78-86
.storybook/preview.ts (1)
5-6: 경로 표기 방식 통일 제안(선택)다른 스토리/MDX는 alias(
@/shared/...)를 사용합니다. 여기서도 alias로 통일하면 리팩터 시 상대경로 유지보수 부담이 줄어듭니다. 스토리북 빌더에서 alias 동작만 확인해 주세요.-import theme from '../src/shared/styles/theme'; -import GlobalStyles from '../src/shared/styles/globalStyles'; +import theme from '@/shared/styles/theme'; +import GlobalStyles from '@/shared/styles/globalStyles';src/pages/groupNameSetup/GroupNameSetupPage.styles.tsx (1)
3-8: 동일 레이아웃 래퍼 중복 — shared로 승격 권장PasswordSetup, MemberSetup에도 동일한
PageContentWrapper가 있어 중복입니다.shared/styles등으로 승격해 단일 소스 유지가 좋겠습니다.추가 확인:
theme.unit[28],theme.unit[20]인덱스가 테마에 실제 존재하는지 점검 부탁드립니다(미정의 시 런타임 스타일 깨짐).src/pages/groupNameSetup/GroupNameSetupPage.tsx (2)
25-35: useForm 제네릭 지정으로 타입 안전성 보강폼 데이터 타입을 명시하면
register,handleSubmit사용 시 자동 완성/검증이 강화됩니다.적용 diff:
- } = useForm({ + } = useForm<z.infer<typeof groupNameSchema>>({ resolver: zodResolver(groupNameSchema), mode: 'onChange', });
49-68: 엔터 제출 및 기본 검증 충돌 방지 — form으로 래핑현재 버튼 onClick만 있어 엔터키로 제출이 되지 않습니다.
<form onSubmit>으로 감싸고 버튼을type="submit"으로 바꾸면 접근성과 UX가 좋아집니다.적용 diff:
- <DescriptionField - title={`생성할 모임의\n이름을 입력해주세요.`} - sub="모임 이름은 수정이 불가능해요. " - /> - <S.PageContentWrapper> - <Input placeholder="모또 미팅" {...register('groupName')} /> - {errors.groupName ? ( - <Text as="p" variant="caption" color="semantic.state.danger"> - {errors.groupName?.message?.toString()} - </Text> - ) : null} - </S.PageContentWrapper> - <BottomButtonContainer> - <Button - onClick={handleSubmit((data) => onNext(data.groupName))} - disabled={!isValid} - > - 다음 - </Button> - </BottomButtonContainer> + <form onSubmit={handleSubmit((data) => onNext(data.groupName))} noValidate> + <DescriptionField + title={`생성할 모임의\n이름을 입력해주세요.`} + sub="모임 이름은 수정이 불가능해요. " + /> + <S.PageContentWrapper> + <Input + placeholder="모또 미팅" + aria-invalid={Boolean(errors.groupName)} + aria-describedby={errors.groupName ? 'groupName-error' : undefined} + {...register('groupName')} + /> + {errors.groupName ? ( + <Text id="groupName-error" as="p" variant="caption" color="semantic.state.danger"> + {errors.groupName?.message?.toString()} + </Text> + ) : null} + </S.PageContentWrapper> + <BottomButtonContainer> + <Button type="submit" disabled={!isValid}> + 다음 + </Button> + </BottomButtonContainer> + </form>src/features/expense-management/ui/MemberExpenses/index.styles.ts (1)
14-18: spacing 인덱스 매직넘버 완화 제안
theme.unit[24|64]같은 인덱스는 의미 파악이 어렵습니다.theme.space.xl등 의미형 토큰이나 헬퍼(theme.spacing(24)) 도입을 고려해 주세요.src/features/expense-management/ui/FormField/index.tsx (2)
49-51: Controller 사용 시 control 미지정 방지 가드 추가 제안
renderInput만 주고control이 누락되면Controller가 런타임 에러를 낼 수 있습니다. 가드 추가를 권장합니다.다음과 같이 초기 리턴 가드를 추가해 주세요.
function FormField({ label, required, control, name, register, renderInput, placeholder, subButton, }: FormFieldProps) { + if (renderInput && !control) { + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.error('FormField: renderInput 사용 시 control이 필요합니다.'); + } + return null; + } return (
27-38: 타입으로 사용 시나리오를 분기(Controlled vs Uncontrolled)하면 안정성이 올라갑니다현재
register와renderInput/control을 동시에 전달할 수 있어 모호합니다. Discriminated Union으로 둘 중 하나만 허용하도록 타입을 분리하면 오용을 원천 차단할 수 있습니다. 적용 원하시면 타입/시그니처 리팩터링 패치 드립니다.src/entities/character/config/character.ts (1)
3-15: 리터럴 보존 + 누락 키 컴파일 타임 검증을 동시에 확보하자는 제안
as const satisfies패턴을 쓰면 값 리터럴을 보존하면서도CharacterType의 모든 키가 채워졌는지 컴파일 타임 검증이 강화됩니다.적용 예시(요지):
-export const CHARACTER_IMAGE_SIZE: Record< - CharacterType, - { - big: { - width?: string; - height?: string; - }; - small: { - width?: string; - height?: string; - }; - } -> = { +export const CHARACTER_IMAGE_SIZE = { /* ...기존 매핑 그대로... */ -}; +} as const satisfies Record< + CharacterType, + { + big: { width?: string; height?: string }; + small: { width?: string; height?: string }; + } +>;Also applies to: 57-57
src/pages/confirmStep/ui/ExpenseCardList/lib/categrizeExpensesByDateWithIndex.ts (1)
10-10: 함수명 오탈자(categrize) — named export로 점진적 교체 권장src/pages/confirmStep/ui/ExpenseCardList/lib/categrizeExpensesByDateWithIndex.ts는 default export로 사용(index.tsx에서 import·호출 확인)되므로 즉시 이름 변경 대신 별칭(named export)을 추가하세요.
// 내부 구현/기존 default export는 유지 +export const categorizeExpensesByDateWithIndex = categrizeExpensesByDateWithIndex;src/app/Router.tsx (1)
10-14: 경로에 슬래시가 불필요하게 포함되어 있습니다
@/pages/billDetail/경로의 마지막 슬래시는 제거하는 것이 좋습니다.const BillDetail = lazy(() => - import('@/pages/billDetail/').then(({ BillDetailPage }) => ({ + import('@/pages/billDetail').then(({ BillDetailPage }) => ({ default: BillDetailPage, })) );src/pages/billDetail/ui/ExpenseMembers/index.tsx (2)
5-8: status 타입을 구체화해 안정성 강화 제안문자열 전반 허용보다는 리터럴 유니온으로 제한하면 하위 컴포넌트와의 계약이 명확합니다.
아래처럼 최소 수정으로 제안드립니다:
interface ExpenseMembersProps { groupToken: string; - status: string; + status: 'paid' | 'unpaid'; }
24-35: 빈 목록 처리(UI 상태) 추가 제안데이터는 성공이지만 항목이 0개인 경우를 명시적으로 처리하면 UX가 개선됩니다.
if (isError || !memberExpenseData) { return <div>error...</div>; } - return ( + if (memberExpenseData.length === 0) { + return <S.Wrapper>아직 멤버 정산 내역이 없어요.</S.Wrapper>; + } + + return ( <S.Wrapper> {memberExpenseData.map((member) => ( <ExpenseMemberItem key={member.id} member={member} groupToken={groupToken} status={status} /> ))} </S.Wrapper> );src/entities/settlement/model/groupMember.type.ts (1)
1-1: 모듈 경계 주석(TODO) 정리 필요도메인 소유권이 불분명하면 추후 순환참조/누수 위험이 있습니다. 주석 대신 이슈로 추적하고 실제로 settlement‑detail 쪽으로 이동 여부를 결정해주세요.
원하시면 타입 배치 기준(entities vs features) 정리안과 이동 PR 템플릿을 드릴게요.
src/features/expense-management/api/useGetExpenseDetail.ts (1)
4-9: query 활성화 가드 추가 제안빈 문자열/undefined 토큰으로 불필요한 호출을 방지하세요.
return useQuery({ queryKey: ['expenseDetail', groupToken], queryFn: () => expense.getDetail(groupToken), + enabled: !!groupToken, });src/app/GlobalErrorBoundary/index.tsx (1)
8-10: 오류 로깅 연동(onError) 추천전역 바운더리에서 로깅/모니터링으로 전달되도록 onError를 추가하세요.
function GlobalErrorBoundary({ children }: GlobalErrorBoundaryProps) { - return <ErrorBoundary fallback={<ErrorPage />}>{children}</ErrorBoundary>; + return ( + <ErrorBoundary + fallback={<ErrorPage />} + onError={(error, info) => { + // TODO: 앱 로거/모니터링으로 교체(Sentry 등) + console.error('[GlobalErrorBoundary]', error, info); + }} + > + {children} + </ErrorBoundary> + ); }src/entities/group/api/group.ts (4)
10-14: 쿼리스트링 수동 조립 → axios params로 전환 제안URL 인코딩/캐싱 키 일관성을 위해 params 옵션이 안전합니다.
get: (groupToken: string): Promise<Group> => axiosInstance - .get(`/group?groupToken=${groupToken}`) + .get('/group', { params: { groupToken } }) .then((res) => res.data),
24-36: PUT 요청도 params 사용 및 응답 타입 명시명시적 제네릭으로 타입 안정성 확보를 권장합니다.
export const putGroupAccount = async ({ accountData, groupToken, }: { accountData: AccountVariable; groupToken: string; }) => { - const response = await axiosInstance.put( - `/group/account?groupToken=${groupToken}`, - accountData - ); + const response = await axiosInstance.put<void>( + '/group/account', + accountData, + { params: { groupToken } } + ); return response.data; };
38-44: 헤더 API도 params로 통일동일한 이유로 params 사용 권장합니다.
export const getGroupHeader = ( groupToken: string ): Promise<GroupHeaderResponse> => { return axiosInstance - .get(`/group/header?groupToken=${groupToken}`) + .get('/group/header', { params: { groupToken } }) .then((res) => res.data); };
1-1: API 내보내기 방식 일관화 제안기본 export(object)와 개별 named export가 혼재합니다. 팀 컨벤션에 맞춰 하나로 통일하면 DX가 좋아집니다(예: 모두 named).
src/pages/billDetail/ui/ExpenseTimeline/index.tsx (1)
2-2: 상대 경로 사용을 고려해보세요.현재
../../../../features/expense-management/api/useGetExpenseDetail로 상대 경로를 사용하고 있지만, FSD에서는 절대 경로@/features/expense-management/api/useGetExpenseDetail를 사용하는 것이 더 일관성 있고 유지보수에 유리합니다.다음과 같이 절대 경로로 변경하는 것을 권장합니다:
-import useGetExpenseDetail from '../../../../features/expense-management/api/useGetExpenseDetail'; +import useGetExpenseDetail from '@/features/expense-management/api/useGetExpenseDetail';src/entities/character/api/image.ts (1)
6-9: Axios 요청: 쿼리스트링 직접 조립 대신params와 제네릭 사용 권장타이핑을 강화하고 인코딩 문제를 예방합니다.
- getCharacter: (groupToken: string): Promise<CharacterData> => - axiosInstance - .get(`/character?groupToken=${groupToken}`) - .then((res) => res.data), + getCharacter: (groupToken: string) => + axiosInstance + .get<CharacterData>('/character', { params: { groupToken } }) + .then((res) => res.data),src/pages/billDetail/ui/ExpenseTimelineContent/index.tsx (3)
21-23: 가격 표시 로케일 고정 제안 (toLocaleString)브라우저/환경에 따라 포맷이 달라질 수 있어
ko-KR로케일 고정을 권장합니다.- <Text variant="heading2" color="semantic.text.strong"> - {expense.totalAmount.toLocaleString()}원 - </Text> + <Text variant="heading2" color="semantic.text.strong"> + {expense.totalAmount.toLocaleString('ko-KR')}원 + </Text>
27-35: 토글 상태(isExpanded)가 UI에 반영되지 않음토글 상태를 세팅하지만 리스트 표시 제어에 사용하지 않고 있습니다. 목적이 항상 노출이라면 상태/버튼 제거, 토글 UI라면 조건부 렌더링을 적용해 주세요.
- <S.MemberChipHeader> - <Button variant="text" onClick={() => setIsExpanded(!isExpanded)}> - <Text>{expense.groupMembers.length}명</Text> - </Button> - </S.MemberChipHeader> - <S.MemberChipList> - {expense.groupMembers.map((name) => ( - <Chip key={name} label={name} variant="disabled" size="sm" /> - ))} - </S.MemberChipList> + <S.MemberChipHeader> + <Button + variant="text" + aria-expanded={isExpanded} + onClick={() => setIsExpanded((v) => !v)} + > + <Text>{expense.groupMembers.length}명</Text> + </Button> + </S.MemberChipHeader> + {isExpanded && ( + <S.MemberChipList> + {expense.groupMembers.map((name, idx) => ( + <Chip key={`${name}-${idx}`} label={name} variant="disabled" size="sm" /> + ))} + </S.MemberChipList> + )}
32-34: Chip key 충돌 가능성 (동명이인)
name만 key로 쓰면 중복 위험이 있습니다. index와 조합 등으로 충돌을 회피하세요.- {expense.groupMembers.map((name) => ( - <Chip key={name} label={name} variant="disabled" size="sm" /> - ))} + {expense.groupMembers.map((name, idx) => ( + <Chip key={`${name}-${idx}`} label={name} variant="disabled" size="sm" /> + ))}src/features/expense-management/ui/NumPadBottomSheet/index.tsx (3)
25-31: 시트 열림/초기값 변경 시 입력 상태 동기화 필요
initialValue가 바뀌거나 시트를 다시 열 때 내부input이 과거 값으로 남을 수 있습니다. 열릴 때 최신 값으로 리셋하는 효과를 추가하세요.useEffect(() => { // 500만원 이상은 입력 불가능하도록 처리 if (input > 5_000_000) { setInput(5_000_000); } }, [input]); + + // 시트 열림 또는 initialValue 변경 시 입력값 동기화 + useEffect(() => { + if (open) setInput(initialValue); + }, [open, initialValue]);
36-36: 표시 로케일 고정 제안일관된 표기를 위해
toLocaleString('ko-KR')사용 권장.- value={initialValue ? initialValue.toLocaleString() : ''} + value={initialValue ? initialValue.toLocaleString('ko-KR') : ''}
43-47: 닫기 버튼 접근성 보강아이콘 버튼에 레이블이 없어 스크린리더에서 의미가 불명확합니다.
aria-label추가를 권장합니다.- <Button variant="text" onClick={() => setOpen(false)}> + <Button variant="text" aria-label="닫기" onClick={() => setOpen(false)}> <Close width="1.5rem" /> </Button>src/mocks/handlers/groupMember.ts (1)
44-44: 불필요한 콘솔 로그 제거 권장테스트/개발 중이더라도 콘솔 노이즈가 큽니다. 제거 부탁드립니다.
- console.log(body);src/entities/expense/api/expense.ts (1)
11-15: Axios: params 및 제네릭 사용 권장템플릿 문자열로
?groupToken=를 직접 연결한 호출이 확인됨 (src/entities/expense/api/expense.ts: 21-23, 30-32, 41-43). 요청에 params 옵션과 응답 제네릭을 사용해 안전성과 가독성을 개선하세요.- getAll: (groupToken: string): Promise<ExpenseList> => - axiosInstance - .get(`/expenses?groupToken=${groupToken}`) - .then((res) => res.data), + getAll: (groupToken: string): Promise<ExpenseList> => + axiosInstance.get<ExpenseList>('/expenses', { params: { groupToken } }).then((r) => r.data), @@ - }): Promise<void> => - axiosInstance.post(`/expenses?groupToken=${groupToken}`, data), + }): Promise<void> => + axiosInstance.post('/expenses', data, { params: { groupToken } }), @@ - }): Promise<void> => - axiosInstance.delete(`/expenses/${expenseId}?groupToken=${groupToken}`), + }): Promise<void> => + axiosInstance.delete(`/expenses/${expenseId}`, { params: { groupToken } }), @@ - }): Promise<void> => - axiosInstance.put(`/expenses/${expenseId}?groupToken=${groupToken}`, data), + }): Promise<void> => + axiosInstance.put(`/expenses/${expenseId}`, data, { params: { groupToken } }), @@ - getDetail: (groupToken: string): Promise<ExpenseDetailList> => - axiosInstance - .get(`/expenses/details?groupToken=${groupToken}`) - .then((res) => res.data), + getDetail: (groupToken: string): Promise<ExpenseDetailList> => + axiosInstance.get<ExpenseDetailList>('/expenses/details', { params: { groupToken } }).then((r) => r.data),src/pages/confirmStep/ui/ExpenseCardList/index.tsx (1)
1-5: Fragment 및 유틸 네이밍 소소한 정리 제안
- Fragment는
react/jsx-runtime가 아니라react에서 가져오는 것이 일반적입니다.categrizeExpensesByDateWithIndex오탈자(ced→cat)로 보입니다. 나중에 검색성/일관성을 위해 리네이밍 고려해주세요.가능하면 아래처럼 변경 권장:
-import { Fragment } from 'react/jsx-runtime'; +import { Fragment } from 'react';유틸은 파일명/심볼 동시 변경이 필요하므로 별 PR/커밋로 처리 권장합니다.
src/features/settlement-details/api/useGetGroupHeader.ts (1)
10-15: 빈 토큰 호출 방지 플래그 추가 권장
groupToken이 빈 문자열일 가능성에 대비해 enabled 플래그를 넘기는 편이 안전합니다. 래퍼가 옵션 패스스루한다면 아래처럼 추가해주세요.return useQueryWithHandlers({ queryKey: ['groupHeader', groupToken], queryFn: () => getGroupHeader(groupToken), errorHandlers, ignoreBoundaryErrors, + enabled: Boolean(groupToken), });Wrapper가
enabled를 지원하는지 확인 부탁드립니다. 지원하지 않는다면 내부에서 빈 토큰일 때 no-op 처리해도 됩니다.src/features/settlement-details/api/useUpdatePaymentStatus.ts (1)
11-20: mutationKey 및 에러 핸들링 보완 제안중복 클릭 방지/디버깅 편의를 위해
mutationKey를 부여하고, 에러 처리 훅을 추가해 두는 것을 권장합니다.- const mutation = useMutation({ + const mutation = useMutation({ + mutationKey: ['updatePaymentStatus', groupToken, groupMemberId], mutationFn: () => updatePaymentStatus({ groupToken, groupMemberId, isPaid }), onSuccess: () => { // 성공하면 memberExpenseDetails 쿼리를 다시 불러온다. queryClient.invalidateQueries({ queryKey: ['memberExpenseDetails', groupToken], }); }, + // onError: (e) => toast.error('결제 상태 변경에 실패했어요. 다시 시도해 주세요.'), });src/pages/billDetail/ui/ExpenseMemberItem/index.style.ts (2)
51-56: position 없이 z-index 사용
z-index는 position이 설정된 요소에만 의미가 있습니다. 의도대로 상위에 오버레이하려면position: relative(또는 absolute/fixed) 추가가 필요합니다.export const StatusChipButton = styled.button` width: fit-content; height: fit-content; cursor: pointer; z-index: 100; + position: relative; `;
5-17: 스타일드 프롭 누수 가능성 점검 (isPaid)
styled(Accordion)<{ isPaid: boolean }>에서isPaid가 하위 DOM까지 전파되면 경고가 날 수 있습니다. Accordion이 안전하게 필터링해 준다면 괜찮지만, 확실치 않다면 transient prop($isPaid) 사용을 권장합니다.-export const Container = styled(Accordion)<{ isPaid: boolean }>` +export const Container = styled(Accordion)<{ $isPaid: boolean }>` padding: ${({ theme }) => theme.unit[20]}; @@ - background: ${({ theme, isPaid }) => - isPaid + background: ${({ theme, $isPaid }) => + $isPaid ? theme.color.semantic.orange.subtle : theme.color.semantic.background.normal.alternative};호출부에서도
isPaid→$isPaid로 함께 교체가 필요합니다. Accordion이 prop 필터링을 보장한다면 현 상태 유지도 가능하니 확인 부탁드립니다.src/mocks/handlers/group.ts (1)
37-52: POST 핸들러에서 body 파싱await누락 및 목 게이트 일관성
request.json()을await하지 않아 Promise가 로그에 출력됩니다.- 다른 핸들러는
getIsMocked를 사용하는데, 여기만 헤더 직접 검사로 달라 일관성이 떨어집니다.- http.post('/api/v1/group', ({ request }) => { - const isMocked = request.headers.get('X-Mock-Request'); - if (!isMocked || isMocked !== 'true') return passthrough(); + http.post('/api/v1/group', async ({ request }) => { + if (!getIsMocked(request)) return passthrough(); @@ - /** body를 잘 전송했는지 확인용 */ - const body = request.json(); + /** body를 잘 전송했는지 확인용 */ + const body = await request.json(); console.log('response body:', body);src/features/expense-management/ui/MemberExpenses/index.tsx (2)
30-35: 0원 표시 누락 가능성
0은 falsy라 현재 빈 문자열로 렌더링됩니다. 0원도 표기하려면 nullish 체크로 바꾸는 게 안전합니다.- <NumberInput - value={member.amount ? member.amount.toLocaleString() : ''} + <NumberInput + value={ + member.amount != null ? member.amount.toLocaleString() : '' + } readOnly variant="sm" placeholder="금액 입력" />
22-26: 삭제 식별자에 name 사용 — 중복 이름 시 오류 가능
onDelete(member.name)은 이름이 중복되면 잘못된 항목을 삭제할 수 있습니다. 가능하면id사용으로 변경 권장합니다. API/상위 컴포넌트가 name 기반이면 현행 유지하되, 중복 가능성만 점검 부탁드립니다.src/pages/confirmStep/ConfirmStepPage.tsx (4)
10-12: 경로 표기 일관성여기 한 곳만 상대 경로(
../../features/...)를 사용합니다. alias(@/features/...)로 통일하면 이동/리팩터링 내구성이 높아집니다.-import useGetAllExpense from '../../features/expense-management/api/useGetAllExpense'; +import useGetAllExpense from '@/features/expense-management/api/useGetAllExpense';
21-24: useLoaderData 타입 명시제너릭으로 로더 데이터 타입을 지정하면 타입 안전성이 좋아집니다.
- const { groupToken } = useLoaderData(); + const { groupToken } = useLoaderData<{ groupToken: string }>();
25-27: 로딩 UI 통일성
<div>Loading...</div>대신 공용 로더 컴포넌트가 있다면 교체하여 일관성을 맞추는 것을 권장합니다.
41-42: 하드코딩된 색상값 사용헤더
bgColor="#F1F3F5"는 테마 토큰으로 대체하는 편이 테마/다크모드 대응에 유리합니다.테마에 대응하는 토큰이 있는지 확인 부탁드립니다.
src/entities/settlement/api/updatePaymentStatus.ts (1)
12-16: Axios 제네릭/params 활용으로 타입 안전성과 인코딩 보강응답 타입을 제네릭으로 명시하고, query string 직접 합성 대신
params옵션 사용을 권장합니다.다음처럼 변경하면 타입 추론과 URL 인코딩이 안전해집니다.
- const response = await axiosInstance.put( - `/group-members/${groupMemberId}/payment?groupToken=${groupToken}`, - { isPaid } - ); - return response.data; + const { data } = await axiosInstance.put<UpdatePaymentStatusData>( + `/group-members/${groupMemberId}/payment`, + { isPaid }, + { params: { groupToken } } + ); + return data;src/pages/billDetail/ui/ExpenseMemberItem/index.tsx (1)
160-169: 리스트 key 고유성 보장
key={expense.content}는 중복 위험이 있습니다. index 또는 복합키를 사용하세요.-{member.expenses.map((expense) => ( - <S.ExpensesWrapper key={expense.content}> +{member.expenses.map((expense, idx) => ( + <S.ExpensesWrapper key={`${expense.content}-${idx}`}>src/app/RouteErrorElement/index.tsx (1)
1-1: react-router-dom로 import 통일 권장DOM 앱에서는
useRouteError를react-router-dom에서 가져오는 것이 일관됩니다.(위 diff에 포함)
src/entities/expense/api/memberExpense.ts (1)
6-11: 쿼리 인코딩과 응답 타입 명시로 안전성/타입 안정성 보강 제안
- groupToken은 URLSearchParams로 인코딩해 주세요.
- Axios 제네릭으로 응답 스키마를 명시하면 추론이 견고해집니다.
- 서버가
memberExpenses를 누락(204 등)할 때 대비해 기본값 처리 권장.적용 diff:
export const getMemberExpenseDetails = async ( groupToken: string -): Promise<MemberSettlement[]> => { - const response = await axiosInstance.get( - `/member-expenses?groupToken=${groupToken}` - ); - return response.data.memberExpenses; +): Promise<MemberSettlement[]> => { + type ApiResponse = { memberExpenses?: MemberSettlement[] }; + const params = new URLSearchParams({ groupToken }); + const { data } = await axiosInstance.get<ApiResponse>('/member-expenses', { + params, + }); + return data.memberExpenses ?? []; }src/pages/confirmStep/ui/ExpenseCard/index.tsx (2)
41-61: 아이콘 전용 버튼에 접근성 라벨 추가스크린리더 접근성을 위해 aria-label을 부여해 주세요.
적용 diff:
- <Button + <Button variant="text" - onClick={() => { + aria-label="지출 수정" + onClick={() => { onEdit({ expenseId: id, initialExpense: { amount, content, date, memberExpenses, }, }); }} > <CarbonEdit width={20} /> </Button> - {index !== 0 ? ( - <Button variant="text" onClick={handleDelete}> + {index !== 0 ? ( + <Button variant="text" aria-label="지출 삭제" onClick={handleDelete}> <Close width={20} /> </Button> ) : null}
67-69: 통화 포맷 로케일 고정기타 화면에서 'ko-KR'을 사용 중이므로 여기서도 일관화 권장.
적용 diff:
- <Text variant="heading2">{amount.toLocaleString()}</Text> + <Text variant="heading2">{amount.toLocaleString('ko-KR')}</Text>src/pages/billDetail/ui/ExpenseTimeHeader/index.tsx (5)
44-45: 브라우저 환경에서의 타이머 타입 불일치NodeJS.Timeout은 DOM 빌드에서 타입 경고 원인입니다. ReturnType으로 교체해 주세요.
적용 diff:
- const intervalRef = useRef<NodeJS.Timeout | null>(null); + const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
80-86: 타이머 해제 시 ref 초기화 누락중복 clear 방지/상태 정확성을 위해 null로 초기화 권장.
적용 diff:
const stopTimer = () => { if (intervalRef.current) { - clearInterval(intervalRef.current); // 타이머 멈추기 + clearInterval(intervalRef.current); // 타이머 멈추기 + intervalRef.current = null; } };
87-113: setInterval 클로저의 status 정합성 보장status가 변경돼도 이 이펙트가 재구독되지 않아, 클로저에 오래된 status가 남을 수 있습니다. 최신 값을 읽도록 ref를 권장합니다.
적용 diff:
+ const statusRef = useRef(status); + useEffect(() => { + statusRef.current = status; + }, [status]); ... - intervalRef.current = setInterval(() => { + intervalRef.current = setInterval(() => { const now = new Date(); const endDate = new Date(headerData!.deadline); const timeDifference = endDate.getTime() - now.getTime(); if (timeDifference <= 0) { - if (status === 'success') return; + if (statusRef.current === 'success') return;
134-139: 0명 분모 케이스 가드 및 상한 적용0으로 나눔 방지 및 100% 상한을 두는 편이 안전합니다.
적용 diff:
- const percentage = (paidMember / totalMember) * 100; + const percentage = + totalMember > 0 ? Math.min(100, (paidMember / totalMember) * 100) : 0;
140-183: 계좌 문자열 안전 결합null/빈값 혼재 시 공백/undefined 노출 가능. filter(Boolean)로 안전 결합 권장.
적용 diff:
- const accountFormat = `${headerData.bank} ${headerData.accountNumber}`; // 신한 110123456789 + const accountFormat = [headerData.bank, headerData.accountNumber] + .filter(Boolean) + .join(' ');src/pages/addAccountStep/AddAccountStepPage.tsx (1)
95-101: 버튼 활성화 조건 다듬기 제안공백만 입력된 케이스를 막기 위해 trim 적용 권장.
적용 diff:
- disabled={!bankName || !accountNumber} + disabled={!bankName.trim() || !accountNumber.trim()}src/pages/characterShare/CharacterSharePage.tsx (2)
104-106: 캐릭터 사이즈 매핑 누락 대비정의되지 않은 name 키로 인한 런타임 방지용 fallback 제안.
적용 diff:
- ...CHARACTER_IMAGE_SIZE[data.name].big, + ...(CHARACTER_IMAGE_SIZE[data.name]?.big ?? {}),
116-123: 중복 클릭 방지(선택 사항)다운로드 중 버튼 비활성화/로딩 상태를 두면 UX가 안정됩니다. 필요 시 isDownloading state 도입 검토.
src/pages/addExpenseStep/AddExpenseStepPage.tsx (1)
50-53: ref={null} 전달 제거불필요한 ref prop 전달은 타입 경고/예상치 못한 동작을 유발할 수 있습니다. prop 자체를 제거하세요.
적용 diff:
- {fieldArrayReturns.fields.map((field, index) => ( - <FormCard key={field.id} ref={null} index={index} /> - ))} + {fieldArrayReturns.fields.map((field, index) => ( + <FormCard key={field.id} index={index} /> + ))}src/pages/billDetail/BillDetailPage.tsx (3)
23-29: useTheme 이중 호출 통합 제안한 번만 호출하고 단일 객체에서 unit을 구조분해 하세요.
적용 diff:
- const { unit } = useTheme(); + const theme = useTheme(); + const { unit } = theme; ... - const theme = useTheme();
58-67: 홈 이동 시 토큰 제거 동작 일관화헤더 좌측 버튼(라인 58-67)은 토큰을 삭제하지 않고 홈으로 이동합니다. 아래 버튼(라인 96)은 삭제 후 이동. 정책을 하나로 맞추는 편이 좋습니다.
91-100: "정산 완료하기" 버튼 — isChecked 리셋으로 ExpenseTimeHeader 모달 트리거 의도 확인
- src/pages/billDetail/BillDetailPage.tsx: isChecked 상태 선언 및 setIsChecked 전달 (const [isChecked, setIsChecked] = useState(false)).
- src/pages/billDetail/ui/ExpenseTimeHeader/index.tsx: useEffect에서 totalMember === paidMember && !isChecked 일 때 setIsModalOpen(true); setIsChecked(true); 호출(약 102–105줄).
- 결론: 버튼의 onClick에서 setIsChecked(false)는 ExpenseTimeHeader 쪽에서 모달을 열기 위한 플래그 리셋으로 의도된 동작이다.
- 권장(선택): 동작이 비직관적이므로 클릭 핸들러를 명명된 함수로 추상화하거나 인라인 주석 추가로 의도를 명시할 것.
src/features/expense-management/ui/FormCard/index.tsx (5)
53-56: 배열 비교에 JSON.stringify 사용은 가독성은 좋지만 비용/안정성 측면에서 미묘합니다.멤버 수가 늘어날 가능성이 있다면, 키 기반 얕은 비교(이름/ID와 amount만 비교)로 교체를 고려하세요. 순서 변경에 덜 민감한 비교가 필요하면 sort된 키로 비교하세요.
58-66: 남는 금액(remainder) 수령자 고정(first member) 로직, 의도 맞나요?현재 항상 updatedMemberExpenses[0]가 remainder를 가져갑니다. 최근 결제자/방장/선택 기반 등 비즈니스 규칙이 따로 있다면 반영 필요합니다. 최소한 사용자가 지정할 수 있도록 UI 훅을 열어두는 방안을 권장합니다.
94-100: NumPad 입력값의 숫자 캐스팅 보강 권장field.onChange에 문자열이 들어가면 분배 로직에서 암묵 변환에 의존하게 됩니다. 숫자로 명시 캐스팅하거나 RHF에서 valueAsNumber를 사용하세요.
- setValue={(value) => field.onChange(value)} + setValue={(value) => field.onChange(Number(value))}또는 FormField/RHF register 단계에서 valueAsNumber 옵션 사용.
139-145: 이름으로 삭제 시 동명이인 문제name으로 필터링하면 동명이인 모두 삭제됩니다. 고유 ID 기반 삭제로 전환을 권장합니다. 타입(ExpenseFormMember)에 id가 없다면 추가 고려 부탁드립니다.
17-17: react-datepicker CSS 중복 임포트 여부 확인BillDatePicker가 내부에서 스타일을 포함한다면 이 임포트는 중복일 수 있습니다. 컴포넌트 내부 포함 여부를 확인 후 하나로 통일하세요.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (16)
src/shared/assets/pngs/CoinImg.pngis excluded by!**/*.pngsrc/shared/assets/pngs/CryingModdoFace.pngis excluded by!**/*.pngsrc/shared/assets/pngs/EntranceModdo.pngis excluded by!**/*.pngsrc/shared/assets/pngs/Link.pngis excluded by!**/*.pngsrc/shared/assets/pngs/LoginHamImg.pngis excluded by!**/*.pngsrc/shared/assets/pngs/LoginSuccessImg.pngis excluded by!**/*.pngsrc/shared/assets/pngs/LogoImg.pngis excluded by!**/*.pngsrc/shared/assets/pngs/MainHamImg.pngis excluded by!**/*.pngsrc/shared/assets/pngs/MainHamImg2.pngis excluded by!**/*.pngsrc/shared/assets/pngs/Onboarding1.pngis excluded by!**/*.pngsrc/shared/assets/pngs/card_main.pngis excluded by!**/*.pngsrc/shared/assets/pngs/defaultProfileImg.pngis excluded by!**/*.pngsrc/shared/assets/pngs/error-ham.pngis excluded by!**/*.pngsrc/shared/assets/pngs/link_main.pngis excluded by!**/*.pngsrc/shared/assets/pngs/moddoFace.pngis excluded by!**/*.pngsrc/shared/assets/pngs/notfound-ham.pngis excluded by!**/*.png
📒 Files selected for processing (107)
.eslintrc.cjs(2 hunks).storybook/main.ts(1 hunks).storybook/preview.ts(1 hunks).storybook/stories/colors.mdx(1 hunks).storybook/stories/icon-gallery.mdx(1 hunks).storybook/stories/units-radius.mdx(1 hunks)package.json(2 hunks)src/app/App.tsx(1 hunks)src/app/GlobalErrorBoundary/index.tsx(1 hunks)src/app/RouteErrorBoundary/index.tsx(1 hunks)src/app/RouteErrorElement/index.tsx(1 hunks)src/app/Router.tsx(1 hunks)src/common/constants/storageKey.ts(0 hunks)src/common/loaders/index.ts(0 hunks)src/common/utils/getRandomColor.ts(0 hunks)src/entities/auth/api/auth.ts(1 hunks)src/entities/auth/api/useGetGuestToken.ts(1 hunks)src/entities/auth/lib/checkAuth.ts(1 hunks)src/entities/auth/lib/getGroupManagerAuth.ts(1 hunks)src/entities/auth/lib/groupTokenUrlLoader.ts(1 hunks)src/entities/character/api/image.ts(1 hunks)src/entities/character/config/character.ts(1 hunks)src/entities/expense/api/expense.ts(1 hunks)src/entities/expense/api/memberExpense.ts(1 hunks)src/entities/expense/lib/getTotalExpense.ts(1 hunks)src/entities/expense/model/expense.type.ts(1 hunks)src/entities/group/api/group.ts(1 hunks)src/entities/group/api/groupMembers.ts(1 hunks)src/entities/group/model/group.type.ts(1 hunks)src/entities/settlement/api/updatePaymentStatus.ts(1 hunks)src/entities/settlement/model/groupMember.type.ts(1 hunks)src/entities/settlement/model/settlement.type.ts(1 hunks)src/features/character-management/api/useGetCharacter.ts(1 hunks)src/features/character-management/ui/CharacterBottomSheet/index.tsx(1 hunks)src/features/character-management/ui/StarChip/index.tsx(1 hunks)src/features/expense-management/api/useCreateExpense.ts(1 hunks)src/features/expense-management/api/useGetAllExpense.ts(1 hunks)src/features/expense-management/api/useGetExpenseDetail.ts(1 hunks)src/features/expense-management/api/useGetMemberExpenseDetails.ts(1 hunks)src/features/expense-management/api/usePutUpdateAccount.ts(1 hunks)src/features/expense-management/api/useUpdateExpense.ts(1 hunks)src/features/expense-management/lib/createBillFunnel.type.ts(1 hunks)src/features/expense-management/lib/useAddExpenseFormArray.ts(1 hunks)src/features/expense-management/ui/FormCard/index.tsx(1 hunks)src/features/expense-management/ui/FormField/index.tsx(1 hunks)src/features/expense-management/ui/MemberExpenses/index.styles.ts(1 hunks)src/features/expense-management/ui/MemberExpenses/index.tsx(1 hunks)src/features/expense-management/ui/NumPad/index.styles.ts(1 hunks)src/features/expense-management/ui/NumPad/index.tsx(1 hunks)src/features/expense-management/ui/NumPad/numPadController.tsx(1 hunks)src/features/expense-management/ui/NumPadBottomSheet/index.tsx(1 hunks)src/features/expense-management/ui/NumberInput/index.styles.ts(1 hunks)src/features/expense-management/ui/NumberInput/index.tsx(1 hunks)src/features/group-creation/api/useGetGroupBasicInfo.ts(1 hunks)src/features/group-creation/api/usePostCreateGroup.ts(1 hunks)src/features/settlement-details/api/useGetGroupHeader.ts(1 hunks)src/features/settlement-details/api/useUpdatePaymentStatus.ts(1 hunks)src/main.tsx(1 hunks)src/mocks/handlers/expense.ts(1 hunks)src/mocks/handlers/group.ts(1 hunks)src/mocks/handlers/groupMember.ts(1 hunks)src/pages/addAccountStep/AddAccountStepPage.tsx(2 hunks)src/pages/addAccountStep/index.ts(1 hunks)src/pages/addAccountStep/ui/BankNameDrawer/index.style.ts(1 hunks)src/pages/addAccountStep/ui/BankNameDrawer/index.tsx(1 hunks)src/pages/addExpenseStep/AddExpenseStepPage.tsx(2 hunks)src/pages/addExpenseStep/index.ts(1 hunks)src/pages/auth/loginSuccess/index.style.ts(0 hunks)src/pages/auth/loginSuccess/index.tsx(0 hunks)src/pages/billDetail/BillDetailPage.tsx(2 hunks)src/pages/billDetail/index.ts(1 hunks)src/pages/billDetail/ui/CurvedProgressBar/index.tsx(1 hunks)src/pages/billDetail/ui/ExpenseMemberItem/index.style.ts(1 hunks)src/pages/billDetail/ui/ExpenseMemberItem/index.tsx(1 hunks)src/pages/billDetail/ui/ExpenseMemberItem/ui/StatusChip/index.styles.ts(1 hunks)src/pages/billDetail/ui/ExpenseMembers/index.tsx(1 hunks)src/pages/billDetail/ui/ExpenseTimeHeader/index.style.ts(1 hunks)src/pages/billDetail/ui/ExpenseTimeHeader/index.tsx(1 hunks)src/pages/billDetail/ui/ExpenseTimeHeader/index.type.ts(1 hunks)src/pages/billDetail/ui/ExpenseTimeline/index.tsx(1 hunks)src/pages/billDetail/ui/ExpenseTimelineContent/index.styles.ts(1 hunks)src/pages/billDetail/ui/ExpenseTimelineContent/index.tsx(1 hunks)src/pages/billDetail/ui/Tabs/index.stories.tsx(1 hunks)src/pages/billDetail/ui/Tabs/index.tsx(1 hunks)src/pages/characterShare/CharacterSharePage.tsx(2 hunks)src/pages/characterShare/index.ts(1 hunks)src/pages/confirmStep/ConfirmStepPage.tsx(3 hunks)src/pages/confirmStep/index.ts(1 hunks)src/pages/confirmStep/ui/ExpenseCard/api/useDeleteExpense.ts(1 hunks)src/pages/confirmStep/ui/ExpenseCard/index.styles.ts(1 hunks)src/pages/confirmStep/ui/ExpenseCard/index.tsx(1 hunks)src/pages/confirmStep/ui/ExpenseCardList/index.tsx(1 hunks)src/pages/confirmStep/ui/ExpenseCardList/lib/categrizeExpensesByDateWithIndex.ts(1 hunks)src/pages/createBill/CreateBillPage.tsx(5 hunks)src/pages/createBill/addAccountStep/utils/BankList.ts(0 hunks)src/pages/createBill/components/MemberBottomSheet/index.tsx(0 hunks)src/pages/createBill/index.ts(1 hunks)src/pages/createExpenseStep/CreateExpenseStepPage.tsx(2 hunks)src/pages/createExpenseStep/index.ts(1 hunks)src/pages/editExpenseStep/EditExpenseStepPage.tsx(2 hunks)src/pages/editExpenseStep/index.ts(1 hunks)src/pages/error/ErrorPage.style.ts(1 hunks)src/pages/error/ErrorPage.tsx(1 hunks)src/pages/error/index.ts(1 hunks)src/pages/groupNameSetup/GroupNameSetupPage.styles.tsx(1 hunks)src/pages/groupNameSetup/GroupNameSetupPage.tsx(3 hunks)src/pages/groupNameSetup/index.ts(1 hunks)
⛔ Files not processed due to max files limit (60)
- src/pages/groupSetup/GroupSetupPage.tsx
- src/pages/groupSetup/index.styles.ts
- src/pages/groupSetup/index.ts
- src/pages/home/HomePage.tsx
- src/pages/home/index.ts
- src/pages/home/ui/HomeExpenseItem/index.style.ts
- src/pages/home/ui/HomeExpenseItem/index.tsx
- src/pages/login/LoginPage.tsx
- src/pages/login/index.ts
- src/pages/memberSetup/MemberSetupPage.styles.tsx
- src/pages/memberSetup/MemberSetupPage.tsx
- src/pages/memberSetup/index.ts
- src/pages/memberSetup/ui/AddMember/api/useAddGroupMember.ts
- src/pages/memberSetup/ui/AddMember/api/useDeleteGroupMember.ts
- src/pages/memberSetup/ui/AddMember/index.stories.tsx
- src/pages/memberSetup/ui/AddMember/index.styles.ts
- src/pages/memberSetup/ui/AddMember/index.tsx
- src/pages/notFound/NotFoundPage.style.ts
- src/pages/notFound/NotFoundPage.tsx
- src/pages/notFound/index.ts
- src/pages/onboarding/OnboardingPage.tsx
- src/pages/onboarding/index.ts
- src/pages/passwordSetup/PasswordSetupPage.styles.tsx
- src/pages/passwordSetup/PasswordSetupPage.tsx
- src/pages/passwordSetup/index.ts
- src/pages/qrStep/QrStepPage.tsx
- src/pages/qrStep/index.ts
- src/pages/selectGroup/SelectGroupPage.styles.ts
- src/pages/selectGroup/SelectGroupPage.tsx
- src/pages/selectGroup/index.ts
- src/pages/shareStep/ShareStepPage.tsx
- src/pages/shareStep/index.ts
- src/shared/api/axios.ts
- src/shared/config/route.ts
- src/shared/hooks/useApiError.ts
- src/shared/hooks/useMutationWithHanders.ts
- src/shared/hooks/useQueryWithHandlers.ts
- src/shared/lib/generateShareLink.ts
- src/shared/lib/getColorFromTheme.ts
- src/shared/lib/processStyle.ts
- src/shared/lib/shareFormat.ts
- src/shared/lib/shareKakao.ts
- src/shared/styles/bottomButton.styles.ts
- src/shared/styles/theme.type.ts
- src/shared/types/styled.d.ts
- src/shared/ui/Accordion/index.tsx
- src/shared/ui/Alert/index.tsx
- src/shared/ui/BottomSheet/index.stories.tsx
- src/shared/ui/Button/index.stories.tsx
- src/shared/ui/Button/index.styles.ts
- src/shared/ui/ButtonGroup/index.stories.tsx
- src/shared/ui/Chip/index.stories.tsx
- src/shared/ui/Chip/index.styles.ts
- src/shared/ui/DatePicker/index.styles.ts
- src/shared/ui/DatePicker/index.tsx
- src/shared/ui/DescriptionField/index.stories.tsx
- src/shared/ui/DescriptionField/index.styles.ts
- src/shared/ui/DescriptionField/index.tsx
- src/shared/ui/Flex/index.style.ts
- src/shared/ui/Flex/index.tsx
💤 Files with no reviewable changes (7)
- src/common/loaders/index.ts
- src/pages/auth/loginSuccess/index.style.ts
- src/common/constants/storageKey.ts
- src/pages/createBill/components/MemberBottomSheet/index.tsx
- src/pages/auth/loginSuccess/index.tsx
- src/pages/createBill/addAccountStep/utils/BankList.ts
- src/common/utils/getRandomColor.ts
🧰 Additional context used
🧬 Code graph analysis (5)
src/entities/settlement/api/updatePaymentStatus.ts (1)
src/entities/settlement/model/groupMember.type.ts (2)
UpdatePaymentStatusVariable(2-6)UpdatePaymentStatusData(8-15)
src/entities/expense/api/memberExpense.ts (1)
src/entities/settlement/model/settlement.type.ts (1)
MemberSettlement(1-13)
src/pages/editExpenseStep/EditExpenseStepPage.tsx (1)
src/features/expense-management/lib/createBillFunnel.type.ts (1)
EditBillContext(3-6)
src/pages/billDetail/ui/ExpenseMemberItem/index.tsx (1)
src/entities/settlement/model/settlement.type.ts (1)
MemberSettlement(1-13)
src/pages/groupNameSetup/GroupNameSetupPage.styles.tsx (2)
src/pages/passwordSetup/PasswordSetupPage.styles.tsx (1)
PageContentWrapper(3-8)src/pages/memberSetup/MemberSetupPage.styles.tsx (1)
PageContentWrapper(3-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Chromatic
Co-authored-by: 심여은 <[email protected]>
|
✨ Storybook preview : https://6867dc9b2bbd1b3d76b92f4a-mecsqzxtpn.chromatic.com/ |
ongheong
left a 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.
LGTM👍🏻
|
@coderabbitai resolve |
✅ Actions performedComments resolved and changes approved. |
fsd 작업하면서 지출 용어를 bill에서 expense로 변경하기로 결정함. 변경하지 못했던 부분을 수정 #7
💻 작업 내용
폴더 구조 변경했어요
Summary by CodeRabbit