Skip to content

Conversation

@yoouyeon
Copy link
Contributor

@yoouyeon yoouyeon commented Sep 21, 2025

💻 작업 내용

폴더 구조 변경했어요

Summary by CodeRabbit

  • Refactor
    • 앱 전반 모듈 경로/에일리어스 재구성 및 페이지 컴포넌트 명칭 통일(...Page), 라우터 및 지연 로딩 방식 정리.
    • 다수의 import/재배치로 구조화 및 빌드/번들 해소 개선.
  • New Features
    • 정산 멤버 결제 상태 갱신 API(updatePaymentStatus) 추가.
    • 일부 페이지에 새 스타일 컴포넌트(PageContentWrapper) 추가.
  • Documentation
    • Storybook 스토리 탐색 범위 확대(./**/*.mdx) 및 경로 정비.
  • Chores
    • ESLint 설정·devDependency 업데이트 및 svgr 스크립트 경로 조정.
  • Removed
    • LoginSuccess 페이지 및 여러 사용되지 않는 유틸/상수/컴포넌트 삭제.

yoouyeon and others added 15 commits September 14, 2025 15:18
group setup 페이지 스타일 import 해결

Co-authored-by: 심여은 <[email protected]>
ShareButton 컴포넌트를 shared로 이동

Co-authored-by: 심여은 <[email protected]>
완전한 전환 후에 사용하기 위해서 우선 주석처리해둠

Co-authored-by: 심여은 <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Sep 21, 2025

Walkthrough

프로젝트 폴더와 모듈 경로를 대규모로 재조정했습니다. 공용 자원은 shared/, 도메인 자원은 entities/로 이동했고 다수의 페이지 컴포넌트가 *Page로 이름 변경·재내보내기되었습니다. 라우터, Storybook, ESLint, mocks, 일부 타입/함수의 추가·삭제도 포함됩니다.

Changes

Cohort / File(s) Change summary
ESLint & Dependencies
./.eslintrc.cjs, ./package.json
ESLint overrides 대상 경로를 src/shared/assets/**로 조정, 주석 처리된 확장 항목 추가(// @feature-sliced), @feature-sliced/eslint-configeslint-plugin-boundaries 추가, eslint-plugin-import 버전 업데이트, svgr 스크립트 경로 변경.
Storybook Config & Stories
./.storybook/main.ts, ./.storybook/preview.ts, .storybook/stories/*
Storybook stories glob에 ./**/*.mdx 추가, preview 및 stories 파일의 theme/GlobalStyles import를 @/shared/styles/...로 정비.
App Bootstrap & Router
src/main.tsx, src/app/App.tsx, src/app/Router.tsx, src/app/GlobalErrorBoundary/*, src/app/RouteError*/*
앱 진입부 및 라우터/에러 경계의 import 경로를 shared/entities로 정리, ErrorPage를 named export로 사용하도록 변경, 라우트 lazy import 패턴 조정(명명→default 매핑), LoginSuccess 라우트 제거.
Entities: API/Types Migration
src/entities/**/api/*, src/entities/**/model/*, src/entities/**/lib/*
axiosInstance@/shared/api/axios로 통일, 타입·모듈 import를 @/entities/.../model로 이동, 일부 API 반환 타입 및 시그니처(예: memberExpenseMemberSettlement[]) 조정, groupMembers.updatePaymentStatus 제거 및 settlement/api/updatePaymentStatus.ts 추가.
Settlement Models Update
src/entities/settlement/model/settlement.type.ts, .../groupMember.type.ts
MemberExpenseMemberSettlement로 이름 변경, MemberExpenseData 제거, 주석 추가(타입 구조 변경).
Expense & Character Feature Refactors
src/features/expense-management/**, src/features/character-management/**
다수 훅·UI·유틸의 import 경로를 @/shared/*@/entities/*로 이동(NumPad, NumberInput, FormCard 등), 이미지/character 관련 import 경로 정리. 기능 시그니처는 대부분 유지.
Pages Renaming & Barrels
src/pages/**/*Page.tsx, src/pages/*/index.ts
다수의 페이지 컴포넌트를 *Page로 함수명·default export 변경 및 각 디렉토리에 export { default as ...Page } from './...Page' 재내보내기 추가(예: CreateBillPage, AddExpenseStepPage 등).
BillDetail Submodules
src/pages/billDetail/BillDetailPage.tsx, src/pages/billDetail/ui/...
BillDetail → BillDetailPage로 이동·이름 변경, 하위 컴포넌트 import를 @/shared/ui로 정리, 멤버 타입을 MemberSettlement으로 교체, 관련 스토리/스타일 경로 업데이트.
Mocks
src/mocks/handlers/*
mock 핸들러의 타입·유틸 import를 @/entities/*, @/mocks/lib/*, @/shared/assets/*로 정리.
Removals / Deprecations
src/pages/auth/loginSuccess/*, src/pages/createBill/components/MemberBottomSheet/*, src/pages/createBill/addAccountStep/utils/BankList.ts, src/common/constants/storageKey.ts, src/common/loaders/index.ts, src/common/utils/getRandomColor.ts
로그인 성공 페이지 파일 삭제, MemberBottomSheet 컴포넌트 삭제, BankList 유틸 제거, storageKey 상수(ACCESS_TOKEN, GROUP_TOKEN) 제거, loaders barrel에서 checkAuth/groupTokenUrlLoader 재내보내기 제거, 랜덤 컬러 유틸 제거로 퍼블릭 표면 축소.
Story / Style Path Updates
**/index.styles.tsx, **/index.stories.tsx, ...`
TextVariant, Accordion 등 스타일/스토리 import 경로를 @/shared/ui로 변경하고 관련 경로 정리.

Possibly related PRs

Poem

(•ᴗ•)っ🥕
폴더 숲을 뛰어다닌 토끼가 말하네
shared엔 공용, entities엔 도메인 씨앗 심었지
페이지 이름은 Page로 새 옷을 갈아입고
라우터 길목엔 named export 깃발 꽂았네
이야~ 스토리북도 반짝, 정리 끝! ✨

Pre-merge checks and finishing touches

✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/fsd-structure

📜 Recent 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 ad9e433 and c7f49ed.

📒 Files selected for processing (1)
  • package.json (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot requested a review from ongheong September 21, 2025 07:42
@yoouyeon yoouyeon added the 🔨 Refactor 코드 구조 개선 label Sep 21, 2025
@yoouyeon yoouyeon changed the title @coderabbit title @coderabbit 제목 Sep 21, 2025
@github-actions
Copy link

Storybook preview : https://6867dc9b2bbd1b3d76b92f4a-smjvgmfmji.chromatic.com/

@coderabbitai coderabbitai bot changed the title @coderabbit 제목 경로·모듈 구조 재구성 및 라우터·설정 갱신 제목 Sep 21, 2025
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: 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.dateDate, 다른 곳은 string

API 경계에서 직렬화/역직렬화 혼선을 유발합니다. 일관되게 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>
+      )}

비고: Buttonas/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: 버그: 상세 조회 엔드포인트에 선행 슬래시 누락

getDetailexpenses/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.tsindex.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 타입 명시로 일관성/오류 탐지 강화

동일 디렉터리의 getGroupManagerAuthLoaderFunction을 사용합니다. 이 함수도 로더로 쓰인다면 타입을 명시해 일관성과 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} 제거 제안

FormCardforwardRef를 사용하지 않거나 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)하면 안정성이 올라갑니다

현재 registerrenderInput/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 앱에서는 useRouteErrorreact-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

📥 Commits

Reviewing files that changed from the base of the PR and between 162ec8e and ad9e433.

⛔ Files ignored due to path filters (16)
  • src/shared/assets/pngs/CoinImg.png is excluded by !**/*.png
  • src/shared/assets/pngs/CryingModdoFace.png is excluded by !**/*.png
  • src/shared/assets/pngs/EntranceModdo.png is excluded by !**/*.png
  • src/shared/assets/pngs/Link.png is excluded by !**/*.png
  • src/shared/assets/pngs/LoginHamImg.png is excluded by !**/*.png
  • src/shared/assets/pngs/LoginSuccessImg.png is excluded by !**/*.png
  • src/shared/assets/pngs/LogoImg.png is excluded by !**/*.png
  • src/shared/assets/pngs/MainHamImg.png is excluded by !**/*.png
  • src/shared/assets/pngs/MainHamImg2.png is excluded by !**/*.png
  • src/shared/assets/pngs/Onboarding1.png is excluded by !**/*.png
  • src/shared/assets/pngs/card_main.png is excluded by !**/*.png
  • src/shared/assets/pngs/defaultProfileImg.png is excluded by !**/*.png
  • src/shared/assets/pngs/error-ham.png is excluded by !**/*.png
  • src/shared/assets/pngs/link_main.png is excluded by !**/*.png
  • src/shared/assets/pngs/moddoFace.png is excluded by !**/*.png
  • src/shared/assets/pngs/notfound-ham.png is 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

@github-actions
Copy link

Storybook preview : https://6867dc9b2bbd1b3d76b92f4a-mecsqzxtpn.chromatic.com/

Copy link
Contributor

@ongheong ongheong left a comment

Choose a reason for hiding this comment

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

LGTM👍🏻

@yoouyeon
Copy link
Contributor Author

yoouyeon commented Nov 8, 2025

@coderabbitai resolve

@coderabbitai
Copy link

coderabbitai bot commented Nov 8, 2025

✅ Actions performed

Comments resolved and changes approved.

@yoouyeon yoouyeon merged commit 739f485 into develop Nov 8, 2025
2 checks passed
@yoouyeon yoouyeon deleted the refactor/fsd-structure branch November 8, 2025 14:10
yoouyeon added a commit that referenced this pull request Dec 14, 2025
fsd 작업하면서 지출 용어를 bill에서 expense로 변경하기로 결정함.
변경하지 못했던 부분을 수정
#7
@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

🔨 Refactor 코드 구조 개선

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants