-
Notifications
You must be signed in to change notification settings - Fork 0
모임 선택 화면에 기존 모임 선택 기능 추가 #10
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
base: develop
Are you sure you want to change the base?
Conversation
로그인이 필요한 경로들을 그룹화
- QueryClient를 queryClient.ts에서 관리하도록 변경 - QueryClient 기본 에러 핸들러를 설정할 수 있는 함수 추가
- 카카오 로그인 함수 분리 - 로그인 요청 시점의 state를 전달함 (리다이렉트용)
- 새로운 인증 체크 API 엔드포인트 추가 - 인증 체크를 위한 useGetAuth 훅 생성 - 인증 체크 Mock API 추가
2d11ed2 에서 제거한 온보딩 페이지 다시 추가
Outlet이 없어도 문제는 없었으나, 생략해도 된다는 문서를 찾지 못함...
모듈 로드 시점에 변수를 체크해서 에러 바운더리 쪽에서 처리할 수 있도록 함.
- 모임 생성 페이지로 이동하는 버튼 - 비어있음 표시 버튼 - 기존 모임 선택 버튼
객체 메소드 구조에서 개별 함수로 분리함
fsd 작업하면서 지출 용어를 bill에서 expense로 변경하기로 결정함. 변경하지 못했던 부분을 수정 #7
- group token을 url 파라미터로 받도록 라우트 수정 - loader에서 그룹 토큰만 확인하도록 수정 (로그인 여부는 상위 로더에서 확인) - loader 코드 위치를 페이지 가까이로 이동 (재사용 가능성 낮기 때문)
group 데이터에 대한 쿼리 관리를 편하게 하기 위해서 group/api 디렉토리에 한 파일에 정의해둠
직관적인 이름으로 변경하고, 재사용을 위해 entities 디렉토리로 위치 변경
- 로더 함수 이름 변경 - 모임 토큰 유효성 확인용 모임 데이터 페칭 로직 추가 - 모임 데이터와 토큰을 반환하도록 수정
- groupToken을 사용한 데이터 페칭 로직 제거 - defaultFormValue 계산 시 groupData를 참조하도록 수정
axios의 params 옵션을 사용하여 자동으로 URL 인코딩되도록 수정
버튼 모양이지만 클릭 동작을 쓰지 않는 컴포넌트이므로 Button 대신 Box라는 이름을 씀
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 📝 WalkthroughWalkthrough이 PR은 인증 흐름을 비동기 쿼리 패턴으로 리팩터링하고, 청구(Bill)를 지출(Expense)로 재명명하며, 그룹 API를 함수형으로 재구성하고, 선택 그룹 페이지를 새 UI 컴포넌트로 재구축하고, 중앙 집중식 쿼리 클라이언트 설정을 도입합니다. Changes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches✅ Passed checks (1 passed)
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-xciegopkwi.chromatic.com/ |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
src/features/expense-management/ui/FormCard/index.tsx (1)
114-119: 날짜 파싱/타임존/빈 값에서Invalid Date가능성 있어요.
new Date('yyyy-MM-dd')는 UTC 파싱이라 로컬 타임존에서 날짜가 하루 밀릴 수 있고,field.value가 비어있으면Invalid Date가 됩니다.date-fns로 명시 파싱/가드 추천합니다.-import { format } from 'date-fns'; +import { format, parse } from 'date-fns'; <ExpenseDatePicker - selected={new Date(field.value)} - onChange={(date) => - field.onChange(format(date || new Date(), 'yyyy-MM-dd')) - } + selected={ + field.value + ? parse(field.value, 'yyyy-MM-dd', new Date()) + : undefined + } + onChange={(date) => + field.onChange(date ? format(date, 'yyyy-MM-dd') : '') + } />src/entities/auth/api/auth.ts (1)
4-9: 모의 응답과 인터페이스의 타입 불일치를 수정하세요.
GuestTokenData인터페이스의expiredAt필드는Date타입으로 정의되어 있지만,src/mocks/handlers/auth.ts의/api/v1/user/guest/token엔드포인트 모의 응답에서는 문자열('2025-03-06 14:17:26')로 반환되고 있습니다.isMember필드는 올바르게 포함되어 있습니다.인터페이스의
expiredAt을string타입으로 변경하거나, 모의 응답에서 Date 객체로 변환하여 타입 일관성을 맞춰주세요.src/pages/expenseDetail/ExpenseDetailPage.tsx (1)
44-44: localStorage에서 groupToken 사용을 제거하세요.PR에서 모임 토큰을 URL 쿼리 파라미터로 전달하도록 변경했지만, 현재 코드에는
localStorage.removeItem('groupToken')이 여전히 남아 있습니다. 코드베이스 전체를 확인한 결과,localStorage.getItem('groupToken')은 어디에도 존재하지 않습니다.groupToken은 URL 파라미터의 라우터 로더(
groupTokenUrlLoader)에서만 추출되어 사용되므로, localStorage 저장소와 제거 로직은 더 이상 필요하지 않습니다. 이를 정리하기 위해 다음 두 곳의localStorage.removeItem('groupToken')호출을 제거하세요:
- src/pages/expenseDetail/ExpenseDetailPage.tsx (라인 44)
- src/pages/createExpenseStep/CreateExpenseStepPage.tsx (라인 53)
또한 그룹 생성 성공 시 저장하는
localStorage.setItem('groupToken', response?.groupToken)(src/features/group-creation/api/usePostCreateGroup.ts)도 제거해야 합니다.src/pages/memberSetup/MemberSetupPage.tsx (1)
71-71:reverse()는 원본 배열을 변경하며, 매 렌더링마다 호출됩니다.
Array.prototype.reverse()는 원본 배열을 직접 변경(mutate)하고 매 렌더링마다 호출되어 멤버 순서가 계속 뒤집힙니다.toReversed()또는 스프레드 연산자를 사용하세요.- members={data.members.reverse() || []} + members={[...data.members].reverse() || []}src/mocks/handlers/group.ts (1)
97-112:request.json()을 await하지 않아 디버그 로그가 의도대로 동작하지 않습니다.주석상 "body를 잘 전송했는지 확인용"이므로, handler를
async로 변경하고await를 추가해야 합니다. 현재 상태에서는console.log가 Promise 객체를 출력하게 됩니다.- http.post('/api/v1/group', ({ request }) => { + http.post('/api/v1/group', async ({ request }) => { const isMocked = request.headers.get('X-Mock-Request'); if (!isMocked || isMocked !== 'true') return passthrough(); const accessToken = request.headers.get('Authorization'); if (!accessToken) { return HttpResponse.json({ error: 'Unauthorized' }, { status: 401 }); } /** body를 잘 전송했는지 확인용 */ - const body = request.json(); + const body = await request.json(); console.log('response body:', body);src/features/expense-management/lib/useAddExpenseFormArray.ts (1)
27-49:groupDatanull 체크 누락으로 런타임 에러 발생 가능
defaultValuesasync 함수 내부(Line 39)에서groupData.members에 직접 접근하고 있지만, Line 53의useMemo와 달리 null 체크가 없습니다. 만약 loader가 예외 상황에서groupData를 반환하지 않으면Cannot read properties of undefined (reading 'members')에러가 발생할 수 있습니다.defaultValues: async () => { // 기본 데이터가 있는 경우 (ex. 수정) if (initialExpense) { return { expenses: [initialExpense], }; } // 기본 데이터가 없는 경우 (ex. 추가) + if (!groupData) { + return { expenses: [defaultValues] }; + } return { expenses: [ { ...defaultValues, memberExpenses: groupData.members.map((member) => ({
♻️ Duplicate comments (2)
src/pages/editExpenseStep/EditExpenseStepPage.styles.ts (1)
3-14: 동일한 코드 중복.이 파일의
ExpenseFormList는AddExpenseStepPage.styles.ts와CreateExpenseStepPage.styles.ts에서도 동일하게 정의되어 있습니다. 이전 파일 리뷰에서 제안한 대로 공통 위치로 추출을 권장합니다.src/pages/createExpenseStep/CreateExpenseStepPage.styles.ts (1)
3-14: 동일한 코드 중복.이 파일의
ExpenseFormList도 앞서 리뷰한 두 파일과 동일합니다. 세 파일 모두에서 공통 스타일을 공유 위치로 추출하는 것을 권장합니다.
🧹 Nitpick comments (16)
src/pages/selectGroup/ui/GroupLinkButton.styles.ts (1)
4-15: 스타일 컴포넌트 구현이 적절합니다.CreateGroupLinkButton.styles.ts와 유사한 구조를 가지고 있지만, 배경색과 패딩 값의 차이는 각 버튼의 시각적 위계를 구분하기 위한 의도적인 디자인으로 보입니다.
향후 버튼 스타일이 더 추가될 경우, 공통 속성을 추상화한 기본 LinkButton 컴포넌트를 만들고 variant prop으로 스타일을 구분하는 것을 고려해볼 수 있습니다.
src/pages/addExpenseStep/AddExpenseStepPage.styles.ts (1)
3-14: 코드 중복 제거를 고려해주세요.
ExpenseFormList스타일 컴포넌트가 세 개의 파일에서 동일하게 정의되어 있습니다:
src/pages/addExpenseStep/AddExpenseStepPage.styles.tssrc/pages/createExpenseStep/CreateExpenseStepPage.styles.tssrc/pages/editExpenseStep/EditExpenseStepPage.styles.ts공통 스타일을
src/shared/ui또는src/pages/shared/styles같은 공유 위치로 추출하여 중복을 제거하는 것을 권장합니다.다음과 같이 리팩터링할 수 있습니다:
새 파일
src/shared/ui/ExpenseFormList/index.styles.ts생성:import styled from 'styled-components'; export const ExpenseFormList = styled.form` display: flex; flex-direction: column; gap: ${({ theme }) => theme.unit[24]}; width: 100%; flex: 1 0 0; overflow-y: auto; padding: ${({ theme }) => `0 ${theme.unit[20]} ${theme.unit[24]} ${theme.unit[20]}`}; background: ${({ theme }) => theme.color.semantic.background.normal.alternative}; `;각 페이지 스타일 파일에서:
export { ExpenseFormList } from '@/shared/ui/ExpenseFormList/index.styles';src/pages/selectGroup/ui/CreateGroupLinkButton.styles.ts (1)
4-13: 코드 중복 가능성을 검토하세요.
GroupLinkButton.styles.ts에도 동일한 이름의LinkButton스타일 컴포넌트가 존재합니다. 두 컴포넌트는 배경색과 테두리만 다르고 나머지 스타일이 유사합니다. variant prop을 받는 단일 스타일 컴포넌트로 통합하는 것을 고려해보세요.예시 통합 방식:
export const LinkButton = styled(Link)<{ variant?: 'primary' | 'default' }>` width: 100%; @media (min-width: 600px) { font-size: 22px; } background-color: ${({ theme, variant }) => variant === 'primary' ? theme.color.semantic.orange.default : theme.color.semantic.background.normal.default}; border-radius: ${({ theme }) => theme.radius.default}; ${({ variant, theme }) => variant !== 'primary' && `border: 1px solid ${theme.color.semantic.border.default};`} padding: ${({ theme }) => `${theme.unit[16]} ${theme.unit[8]}`}; cursor: pointer; `;src/pages/selectGroup/ui/CreateGroupLinkButton.tsx (1)
14-14: gap 값의 일관성을 확인하세요.
gap={4}는 숫자 리터럴을 직접 사용하고 있습니다. 다른 테마 값들과의 일관성을 위해gap={theme.unit[4]}또는 유사한 형태로 변경하는 것이 좋을 수 있습니다.src/entities/auth/lib/kakaoLogin.ts (1)
12-14: 모든 쿼리 파라미터에 URL 인코딩 적용 권장
state파라미터는encodeURIComponent를 사용하고 있지만,client_id와redirect_uri도 인코딩하는 것이 안전합니다.URLSearchParams를 사용하면 자동으로 인코딩됩니다.function kakaoLogin(url?: string) { const defaultRedirectUrl = window.location.origin; const redirectUrl = url || defaultRedirectUrl; - window.location.href = `https://kauth.kakao.com/oauth/authorize?client_id=${KAKAO_CLIENT_ID}&redirect_uri=${KAKAO_REDIRECT_URI}&response_type=code&state=${encodeURIComponent( - redirectUrl - )}`; + const params = new URLSearchParams({ + client_id: KAKAO_CLIENT_ID, + redirect_uri: KAKAO_REDIRECT_URI, + response_type: 'code', + state: redirectUrl, + }); + window.location.href = `https://kauth.kakao.com/oauth/authorize?${params.toString()}`; }src/shared/api/queryClient.ts (1)
3-18: QueryCache 생성자를 통해 onError 핸들러를 설정하는 것을 권장합니다.
queryClient.getQueryCache().config.onError를 직접 수정하는 방식은 공식 문서에서 권장하지 않으며, 향후 버전에서 동작이 변경될 수 있습니다.QueryCache와MutationCache생성자 옵션을 사용하세요.-import { QueryClient } from '@tanstack/react-query'; +import { QueryClient, QueryCache, MutationCache } from '@tanstack/react-query'; -export const queryClient = new QueryClient(); - -export const setupQueryClient = ( +export const createQueryClient = ( handleQueryError: (error: Error) => void, handleMutationError: (error: Error) => void -) => { - queryClient.setDefaultOptions({ - mutations: { - onError: handleMutationError, - throwOnError: true, // 기본적으로 RouteErrorBoundary로 에러를 던집니다. - }, - queries: { - throwOnError: true, // 기본적으로 RouteErrorBoundary로 에러를 던집니다. +) => + new QueryClient({ + queryCache: new QueryCache({ + onError: handleQueryError, + }), + mutationCache: new MutationCache({ + onError: handleMutationError, + }), + defaultOptions: { + mutations: { + throwOnError: true, + }, + queries: { + throwOnError: true, + }, }, }); - queryClient.getQueryCache().config.onError = handleQueryError; -};src/pages/CreateExpensePage/lib/createExpensePageGuardLoader.ts (2)
24-27: 에러 메시지 추가 권장
throw new Error()에 메시지가 없으면 디버깅 시 원인 파악이 어렵습니다.// 모임 정보가 없으면 리다이렉트 if (!groupData) { - throw new Error(); + throw new Error('Group data not found'); }
30-39: 에러 핸들링 로직 LGTM401/404 에러 시 selectGroup으로 리다이렉트하고, 그 외 에러는 ErrorBoundary로 전파하는 구조가 적절합니다.
다만,
BoundaryError에 원본 에러 정보를 포함하면 디버깅에 도움이 됩니다.// 그 외에는 에러를 그대로 던진다 - throw new BoundaryError({}); + throw new BoundaryError({ cause: error });src/entities/group/api/groupQueries.ts (2)
5-16: 파라미터 기본값 설정으로 사용성 개선 권장매 호출 시
errorHandlers와ignoreBoundaryErrors를 명시해야 하는 현재 구조는 사용이 번거롭습니다.export const useGetGroupList = ( - errorHandlers: ErrorHandlers, - ignoreBoundaryErrors: IgnoreBoundaryErrors + errorHandlers: ErrorHandlers = {}, + ignoreBoundaryErrors: IgnoreBoundaryErrors = [] ) => {이렇게 하면
useGetGroupList()로 간단히 호출할 수 있습니다.
18-30:groupToken유효성에 따른 쿼리 활성화 제어 권장
groupToken이 빈 문자열이거나 없는 경우에도 쿼리가 실행될 수 있습니다.export const useGetGroupDetail = ( groupToken: string, - errorHandlers: ErrorHandlers, - ignoreBoundaryErrors: IgnoreBoundaryErrors + errorHandlers: ErrorHandlers = {}, + ignoreBoundaryErrors: IgnoreBoundaryErrors = [] ) => { const query = useQueryWithHandlers({ queryKey: ['groupDetail', groupToken], queryFn: () => getGroupDetail(groupToken), errorHandlers, ignoreBoundaryErrors, + enabled: !!groupToken, }); return query; };src/pages/selectGroup/SelectGroupPage.tsx (1)
17-19:groupList가 없을 때 사용자 피드백 개선 권장
groupList가undefined인 경우null을 반환하면 사용자에게 아무것도 표시되지 않습니다. 에러 상태나 재시도 옵션을 제공하는 것이 UX에 더 좋습니다.if (!groupList) { - return null; + return <div>데이터를 불러올 수 없습니다.</div>; }또는
useGetGroupList에서 반환하는isError,error상태를 활용하여 에러 UI를 표시하는 것을 고려해 주세요.src/entities/group/api/group.ts (2)
21-27:createGroup는 반환 타입을 명시하면 호출부 DX가 좋아집니다.
응답 shape를 이미 알고 있으니Promise<{ groupToken: string }>로 고정하는 편이 안전합니다. (mock을 쓰는 중이면useMock도 동일하게)-export const createGroup = async (groupData: CreateGroupData) => { +export const createGroup = async ( + groupData: CreateGroupData +): Promise<{ groupToken: string }> => { const response = await axiosInstance.post<{ groupToken: string }>( '/group', groupData ); return response.data; };
36-49: query param은 문자열 보간 대신params로 인코딩 보장하는 게 안전합니다.
groupToken에 특수문자가 포함되면 깨질 수 있어서 axios의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('/group/account', accountData, { + params: { groupToken }, + }); return response.data; }; @@ export const getGroupHeader = ( groupToken: string ): Promise<GroupHeaderResponse> => { return axiosInstance - .get(`/group/header?groupToken=${groupToken}`) + .get('/group/header', { params: { groupToken } }) .then((res) => res.data); };src/app/Router.tsx (2)
52-114:createBrowserRouter를 컴포넌트 외부로 이동 권장
createBrowserRouter가AppRouter함수 내부에서 호출되고 있어, 매 렌더링마다 새로운 router 인스턴스가 생성됩니다. 이는 불필요한 재생성과 잠재적인 상태 손실을 야기할 수 있습니다.+const router = createBrowserRouter([ + { + id: 'root', + // ... router config + }, +]); + function AppRouter() { - const router = createBrowserRouter([ - // ... - ]); - return <RouterProvider router={router} />; }
58-60: 로딩 fallback UI 개선 고려현재
<div>loading...</div>로 단순 처리되어 있습니다. 추후 디자인 시스템의 로딩 컴포넌트나 스켈레톤 UI로 교체하면 사용자 경험이 향상됩니다.src/pages/CreateExpensePage/CreateExpensePage.tsx (1)
36-41: eslint-disable 주석 정리 고려
react/no-unstable-nested-components경고를 억제하는 주석이 여러 곳에 반복됩니다.@use-funnel라이브러리 사용 패턴상 필요할 수 있지만, 해당 규칙을 파일 레벨에서 비활성화하거나, step 컴포넌트들을 별도 변수로 추출하면 코드가 더 깔끔해질 수 있습니다.파일 상단에서 규칙을 비활성화하는 방법:
/* eslint-disable react/no-unstable-nested-components */Also applies to: 72-75
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (54)
.github/PULL_REQUEST_TEMPLATE.md(1 hunks)src/app/App.tsx(1 hunks)src/app/Router.tsx(3 hunks)src/entities/auth/api/auth.ts(2 hunks)src/entities/auth/lib/checkAuth.ts(1 hunks)src/entities/auth/lib/getGroupManagerAuth.ts(0 hunks)src/entities/auth/lib/kakaoLogin.ts(1 hunks)src/entities/group/api/group.ts(1 hunks)src/entities/group/api/groupQueries.ts(1 hunks)src/entities/group/model/group.type.ts(1 hunks)src/features/character-management/ui/CharacterBottomSheet/index.tsx(1 hunks)src/features/expense-management/lib/createExpenseFunnel.type.ts(1 hunks)src/features/expense-management/lib/useAddExpenseFormArray.ts(3 hunks)src/features/expense-management/ui/FormCard/index.tsx(2 hunks)src/features/group-creation/api/useGetGroupBasicInfo.ts(0 hunks)src/features/group-creation/api/usePostCreateGroup.ts(2 hunks)src/mocks/handlers/auth.ts(1 hunks)src/mocks/handlers/group.ts(1 hunks)src/pages/CreateExpensePage/CreateExpensePage.tsx(5 hunks)src/pages/CreateExpensePage/index.ts(1 hunks)src/pages/CreateExpensePage/lib/createExpensePageGuardLoader.ts(1 hunks)src/pages/addExpenseStep/AddExpenseStepPage.styles.ts(1 hunks)src/pages/addExpenseStep/AddExpenseStepPage.tsx(1 hunks)src/pages/billDetail/index.ts(0 hunks)src/pages/confirmStep/ConfirmStepPage.tsx(1 hunks)src/pages/confirmStep/ui/ExpenseCard/index.tsx(1 hunks)src/pages/confirmStep/ui/ExpenseCardList/index.tsx(1 hunks)src/pages/createBill/index.ts(0 hunks)src/pages/createExpenseStep/CreateExpenseStepPage.styles.ts(1 hunks)src/pages/createExpenseStep/CreateExpenseStepPage.tsx(2 hunks)src/pages/editExpenseStep/EditExpenseStepPage.styles.ts(1 hunks)src/pages/editExpenseStep/EditExpenseStepPage.tsx(2 hunks)src/pages/expenseDetail/ExpenseDetailPage.tsx(2 hunks)src/pages/expenseDetail/index.ts(1 hunks)src/pages/expenseDetail/ui/ExpenseMembers/index.tsx(1 hunks)src/pages/login/LoginPage.tsx(2 hunks)src/pages/memberSetup/MemberSetupPage.tsx(3 hunks)src/pages/selectGroup/SelectGroupPage.styles.ts(0 hunks)src/pages/selectGroup/SelectGroupPage.tsx(2 hunks)src/pages/selectGroup/ui/CreateGroupLinkButton.styles.ts(1 hunks)src/pages/selectGroup/ui/CreateGroupLinkButton.tsx(1 hunks)src/pages/selectGroup/ui/EmptyBox.styles.ts(1 hunks)src/pages/selectGroup/ui/EmptyBox.tsx(1 hunks)src/pages/selectGroup/ui/GroupLinkButton.styles.ts(1 hunks)src/pages/selectGroup/ui/GroupLinkButton.tsx(1 hunks)src/pages/selectGroup/ui/index.ts(1 hunks)src/pages/shareStep/ShareStepPage.tsx(1 hunks)src/shared/api/queryClient.ts(1 hunks)src/shared/config/route.ts(1 hunks)src/shared/lib/generateShareLink.ts(1 hunks)src/shared/ui/NameChip/index.stories.tsx(1 hunks)src/shared/ui/NameChip/index.styles.ts(1 hunks)src/shared/ui/NameChip/index.tsx(1 hunks)src/shared/ui/NameChip/index.type.ts(1 hunks)
💤 Files with no reviewable changes (5)
- src/pages/createBill/index.ts
- src/entities/auth/lib/getGroupManagerAuth.ts
- src/pages/billDetail/index.ts
- src/features/group-creation/api/useGetGroupBasicInfo.ts
- src/pages/selectGroup/SelectGroupPage.styles.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-21T08:03:27.190Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 7
File: src/features/expense-management/ui/FormField/index.tsx:11-13
Timestamp: 2025-09-21T08:03:27.190Z
Learning: src/shared/ui/Button/index.tsx (Line 20), src/shared/ui/Text/index.tsx (Line 35), src/shared/ui/Input/index.tsx (Line 59) 파일들에는 모두 export default가 존재한다. default import 방식 (import Button from '@/shared/ui/Button' 등)이 올바르게 작동한다.
Applied to files:
src/pages/selectGroup/ui/CreateGroupLinkButton.tsxsrc/pages/selectGroup/ui/index.tssrc/pages/CreateExpensePage/index.tssrc/shared/ui/NameChip/index.styles.tssrc/pages/expenseDetail/index.tssrc/pages/confirmStep/ConfirmStepPage.tsxsrc/pages/selectGroup/ui/GroupLinkButton.tsxsrc/pages/editExpenseStep/EditExpenseStepPage.tsxsrc/pages/selectGroup/ui/GroupLinkButton.styles.tssrc/pages/selectGroup/ui/CreateGroupLinkButton.styles.tssrc/features/expense-management/ui/FormCard/index.tsxsrc/pages/selectGroup/ui/EmptyBox.tsxsrc/pages/login/LoginPage.tsx
📚 Learning: 2025-09-21T08:03:27.190Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 7
File: src/features/expense-management/ui/FormField/index.tsx:11-13
Timestamp: 2025-09-21T08:03:27.190Z
Learning: src/shared/ui/Button/index.tsx, src/shared/ui/Text/index.tsx, src/shared/ui/Input/index.tsx 파일들에는 모두 export default가 존재한다. default import 방식 (import Button from '@/shared/ui/Button' 등)이 올바르게 작동한다.
Applied to files:
src/pages/selectGroup/ui/CreateGroupLinkButton.tsxsrc/pages/selectGroup/ui/index.tssrc/pages/CreateExpensePage/index.tssrc/pages/expenseDetail/index.tssrc/pages/editExpenseStep/EditExpenseStepPage.tsxsrc/pages/selectGroup/ui/GroupLinkButton.styles.tssrc/pages/selectGroup/ui/CreateGroupLinkButton.styles.tssrc/features/expense-management/ui/FormCard/index.tsxsrc/pages/selectGroup/ui/EmptyBox.tsx
📚 Learning: 2025-09-21T07:58:28.422Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 7
File: src/features/expense-management/ui/NumberInput/index.tsx:2-2
Timestamp: 2025-09-21T07:58:28.422Z
Learning: src/shared/ui/Text/index.tsx 파일에는 default export가 존재한다 (export default Text;). import Text from '@/shared/ui/Text' 구문이 올바르게 작동한다.
Applied to files:
src/pages/selectGroup/ui/index.tssrc/pages/CreateExpensePage/index.tssrc/pages/expenseDetail/index.ts
📚 Learning: 2025-09-21T07:58:28.422Z
Learnt from: yoouyeon
Repo: moddo-kr/moddo-frontend PR: 7
File: src/features/expense-management/ui/NumberInput/index.tsx:2-2
Timestamp: 2025-09-21T07:58:28.422Z
Learning: src/shared/ui/Text/index.tsx 파일에는 export default Text; 가 35번째 줄에 존재한다. import Text from '@/shared/ui/Text' 구문이 올바르게 작동한다.
Applied to files:
src/pages/selectGroup/ui/index.tssrc/pages/expenseDetail/index.ts
🧬 Code graph analysis (30)
src/pages/editExpenseStep/EditExpenseStepPage.styles.ts (2)
src/pages/addExpenseStep/AddExpenseStepPage.styles.ts (1)
ExpenseFormList(3-14)src/pages/createExpenseStep/CreateExpenseStepPage.styles.ts (1)
ExpenseFormList(3-14)
src/pages/createExpenseStep/CreateExpenseStepPage.styles.ts (2)
src/pages/addExpenseStep/AddExpenseStepPage.styles.ts (1)
ExpenseFormList(3-14)src/pages/editExpenseStep/EditExpenseStepPage.styles.ts (1)
ExpenseFormList(3-14)
src/entities/auth/lib/checkAuth.ts (2)
src/shared/api/queryClient.ts (1)
queryClient(3-3)src/entities/auth/api/auth.ts (1)
getAuth(18-23)
src/entities/group/api/groupQueries.ts (1)
src/entities/group/api/group.ts (2)
getGroupList(9-12)getGroupDetail(14-19)
src/shared/ui/NameChip/index.styles.ts (2)
src/shared/ui/NameChip/index.type.ts (2)
NameChipVariant(3-3)NameChipSize(4-4)src/shared/ui/Text/index.styles.tsx (1)
TextVariant(6-12)
src/features/character-management/ui/CharacterBottomSheet/index.tsx (1)
src/shared/config/route.ts (1)
ROUTE(3-12)
src/pages/confirmStep/ui/ExpenseCardList/index.tsx (2)
src/entities/expense/model/expense.type.ts (1)
Expense(36-48)src/features/expense-management/lib/createExpenseFunnel.type.ts (1)
EditExpenseContext(3-6)
src/pages/confirmStep/ConfirmStepPage.tsx (1)
src/features/expense-management/lib/createExpenseFunnel.type.ts (1)
EditExpenseContext(3-6)
src/shared/ui/NameChip/index.tsx (1)
src/shared/ui/NameChip/index.type.ts (2)
NameChipVariant(3-3)NameChipSize(4-4)
src/pages/memberSetup/MemberSetupPage.tsx (2)
src/entities/group/api/groupQueries.ts (1)
useGetGroupDetail(18-30)src/shared/config/route.ts (1)
ROUTE(3-12)
src/shared/ui/NameChip/index.stories.tsx (1)
src/shared/ui/NameChip/index.type.ts (2)
nameChipVariants(1-1)nameChipSizes(2-2)
src/entities/group/api/group.ts (1)
src/entities/group/model/group.type.ts (2)
Group(3-7)CreateGroupData(14-17)
src/pages/addExpenseStep/AddExpenseStepPage.styles.ts (2)
src/pages/createExpenseStep/CreateExpenseStepPage.styles.ts (1)
ExpenseFormList(3-14)src/pages/editExpenseStep/EditExpenseStepPage.styles.ts (1)
ExpenseFormList(3-14)
src/pages/selectGroup/ui/GroupLinkButton.tsx (3)
src/entities/group/model/group.type.ts (1)
Group(3-7)src/shared/config/route.ts (1)
ROUTE(3-12)src/shared/ui/Text/index.styles.tsx (1)
Text(20-25)
src/pages/editExpenseStep/EditExpenseStepPage.tsx (2)
src/features/expense-management/lib/createExpenseFunnel.type.ts (1)
EditExpenseContext(3-6)src/features/expense-management/ui/FormCard/index.styles.ts (1)
FormCard(7-16)
src/pages/selectGroup/ui/GroupLinkButton.styles.ts (1)
src/pages/selectGroup/ui/CreateGroupLinkButton.styles.ts (1)
LinkButton(4-13)
src/features/expense-management/lib/createExpenseFunnel.type.ts (1)
src/entities/expense/model/expense.type.ts (1)
SingleExpenseForm(25-25)
src/pages/CreateExpensePage/CreateExpensePage.tsx (1)
src/features/expense-management/lib/createExpenseFunnel.type.ts (3)
ExpenseStepContext(8-12)EditExpenseStepContext(14-18)EditExpenseContext(3-6)
src/pages/selectGroup/ui/CreateGroupLinkButton.styles.ts (1)
src/pages/selectGroup/ui/GroupLinkButton.styles.ts (1)
LinkButton(4-15)
src/pages/CreateExpensePage/lib/createExpensePageGuardLoader.ts (2)
src/shared/config/route.ts (1)
ROUTE(3-12)src/entities/group/api/group.ts (1)
getGroupDetail(14-19)
src/features/group-creation/api/usePostCreateGroup.ts (1)
src/entities/group/api/group.ts (1)
createGroup(21-27)
src/pages/selectGroup/SelectGroupPage.tsx (2)
src/entities/group/api/groupQueries.ts (1)
useGetGroupList(5-16)src/pages/selectGroup/ui/EmptyBox.styles.ts (1)
EmptyBox(3-14)
src/app/App.tsx (1)
src/shared/api/queryClient.ts (1)
setupQueryClient(5-19)
src/pages/confirmStep/ui/ExpenseCard/index.tsx (2)
src/entities/expense/model/expense.type.ts (1)
Expense(36-48)src/features/expense-management/lib/createExpenseFunnel.type.ts (1)
EditExpenseContext(3-6)
src/pages/shareStep/ShareStepPage.tsx (1)
src/shared/config/route.ts (1)
ROUTE(3-12)
src/mocks/handlers/group.ts (1)
src/entities/group/model/group.type.ts (1)
Group(3-7)
src/pages/selectGroup/ui/EmptyBox.tsx (2)
src/pages/selectGroup/ui/EmptyBox.styles.ts (1)
EmptyBox(3-14)src/shared/ui/Text/index.styles.tsx (1)
Text(20-25)
src/app/Router.tsx (2)
src/entities/expense/model/expense.type.ts (1)
ExpenseDetail(62-68)src/shared/config/route.ts (1)
ROUTE(3-12)
src/entities/auth/api/auth.ts (1)
public/mockServiceWorker.js (1)
response(125-125)
src/features/expense-management/lib/useAddExpenseFormArray.ts (1)
src/entities/group/model/group.type.ts (1)
Group(3-7)
🔇 Additional comments (42)
.github/PULL_REQUEST_TEMPLATE.md (1)
1-2: 좋은 도움말 추가입니다.CodeRabbit의 자동 제목 생성 기능을 안내하는 주석이 명확하고 기여자들에게 도움이 될 것 같습니다. 템플릿 구조와 기존 내용에 영향을 주지 않으면서 유용한 정보를 추가했습니다.
src/features/expense-management/ui/FormCard/index.tsx (1)
11-12: 네이밍/용어 통일 방향은 좋습니다(ExpenseDatePicker로 교체).
@/shared/ui/DatePicker는 default export를 유지하며, DatePickerProps 인터페이스가 react-datepicker v7.6.0의 기본 prop 시그니처(selected?: Date | null,onChange?: (date: Date | null) => void)와 정확히 일치합니다. 폼의 date 필드는 항상 'yyyy-MM-dd' 형식의 유효한 문자열로 초기화되므로 런타임 에러 리스크도 없습니다.src/pages/expenseDetail/index.ts (1)
1-1: LGTM!표준 barrel export 패턴을 따르고 있으며, ExpenseDetailPage를 명확하게 노출합니다.
src/features/character-management/ui/CharacterBottomSheet/index.tsx (1)
56-56: 라우트 업데이트가 올바릅니다.bill→expense 용어 표준화에 맞춰 ROUTE.characterShare로 정확하게 변경되었습니다.
src/pages/createExpenseStep/CreateExpenseStepPage.tsx (1)
95-108: LGTM!BillFormList에서 ExpenseFormList로의 리네이밍이 전체 bill→expense 용어 표준화와 일관성 있게 적용되었습니다.
src/pages/expenseDetail/ui/ExpenseMembers/index.tsx (1)
2-2: LGTM!디렉토리 구조 변경에 맞춰 import 경로가 정확하게 업데이트되었습니다.
src/pages/confirmStep/ui/ExpenseCardList/index.tsx (1)
4-11: LGTM!EditBillContext에서 EditExpenseContext로의 타입 변경이 import와 prop signature 모두에 일관되게 적용되었습니다.
src/features/group-creation/api/usePostCreateGroup.ts (1)
2-11: LGTM!group API의 리팩터링이 올바르게 반영되었습니다. 객체 메서드에서 독립 함수로의 변경은 테스트 가능성과 코드 명확성을 향상시킵니다.
src/pages/shareStep/ShareStepPage.tsx (1)
60-60: LGTM!정산 내역 확인 버튼의 라우트가 ROUTE.expenseDetail로 정확하게 업데이트되었습니다.
src/mocks/handlers/auth.ts (1)
14-24: LGTM!기존 guest token 핸들러와 동일한 패턴을 따르고 있으며, 인증 체크 mock 응답 구조도 적절합니다.
src/pages/login/LoginPage.tsx (1)
13-13: LGTM!카카오 로그인 기능이 적절하게 통합되었습니다. 기존 placeholder를 실제
kakaoLogin()호출로 교체하여 OAuth 플로우가 정상적으로 작동할 것으로 보입니다.Also applies to: 24-24
src/pages/confirmStep/ui/ExpenseCard/index.tsx (1)
7-7: LGTM!
EditBillContext에서EditExpenseContext로의 타입 변경이 프로젝트 전반의 Bill→Expense 용어 통일과 일치합니다. 타입 이름만 변경되었고 로직은 동일하게 유지됩니다.Also applies to: 13-13
src/pages/selectGroup/ui/EmptyBox.tsx (1)
1-29: LGTM!간단하고 명확한 empty state 컴포넌트입니다. 기존 UI 패턴을 잘 따르고 있으며 구현이 적절합니다.
src/entities/group/model/group.type.ts (1)
4-4: Group 인터페이스의id: string필수 속성 추가는 코드베이스 전체에서 올바르게 반영되었습니다.검증 결과:
- 모든 mock 데이터(src/mocks/handlers/group.ts)에서 Group 객체가 id 필드를 포함합니다.
- SelectGroupPage에서
key={group.id}로 id 필드를 사용하고 있습니다.- GroupLinkButton에서
const { groupName, members, id: groupToken } = group;으로 id를 정상적으로 구조분해합니다.- getGroupList, getGroupDetail API 함수들이 반환하는 데이터는 모두 id 필드를 포함합니다.
- breaking change에 해당하는 불완전한 Group 객체 인스턴스는 발견되지 않았습니다.
src/pages/editExpenseStep/EditExpenseStepPage.tsx (1)
11-11: 일관된 리팩터링이 잘 적용되었습니다.Bill → Expense로의 용어 변경이 타입 정의(
EditExpenseContext)와 UI 컴포넌트(ExpenseFormList)에 일관되게 적용되었습니다.Also applies to: 19-19, 82-86
src/pages/CreateExpensePage/index.ts (1)
1-1: LGTM!표준 배럴 패턴이 올바르게 적용되었습니다.
src/pages/expenseDetail/ExpenseDetailPage.tsx (1)
22-22: 컴포넌트 이름 변경이 일관되게 적용되었습니다.
BillDetailPage에서ExpenseDetailPage로의 리네이밍이 올바르게 적용되었습니다.Also applies to: 109-109
src/shared/lib/generateShareLink.ts (1)
5-5: 라우트 업데이트가 올바르게 적용되었습니다.
ROUTE.billDetail에서ROUTE.expenseDetail로의 변경이 일관된 리팩터링의 일부로 적절하게 적용되었습니다.src/pages/selectGroup/ui/CreateGroupLinkButton.tsx (1)
7-23: 컴포넌트 구현이 잘 되었습니다.접근성을 고려한
aria-label속성이 포함되어 있고, 테마 기반 스타일링이 일관되게 적용되었습니다.src/features/expense-management/lib/createExpenseFunnel.type.ts (1)
3-18: 타입 리네이밍이 일관되게 적용되었습니다.
EditBillContext→EditExpenseContext,BillStepContext→ExpenseStepContext,EditBillStepContext→EditExpenseStepContext로의 변경이 타입 구조 변경 없이 일관되게 적용되었습니다.src/entities/auth/api/auth.ts (1)
18-23: 새로운 인증 확인 함수가 추가되었습니다.
getAuth함수가 추가되어 사용자 인증 상태를 확인할 수 있게 되었습니다.Line 20의
useMock: true플래그가 의도적인지 확인해주세요. 이 설정이 프로덕션 환경에서도 유지되어야 하는지, 아니면 개발 중 임시로 사용하는 것인지 명확히 해주세요.src/pages/selectGroup/ui/EmptyBox.styles.ts (1)
3-14: 미디어 쿼리 내font-size에 대한 기본값 누락 가능성
@media (min-width: 600px)에서만font-size: 22px가 설정되어 있고, 600px 미만 화면에서는 기본font-size가 정의되어 있지 않습니다. 의도된 동작인지 확인해 주세요. 작은 화면에서도 일관된 폰트 크기가 필요하다면 기본값을 추가하는 것이 좋습니다.export const EmptyBox = styled.div` width: 100%; + font-size: 18px; // 또는 적절한 기본값 @media (min-width: 600px) { font-size: 22px; }src/pages/addExpenseStep/AddExpenseStepPage.tsx (1)
49-53: LGTM!PR 목표에 맞게
BillFormList에서ExpenseFormList로 일관되게 변경되었습니다.src/pages/selectGroup/ui/index.ts (1)
1-3: LGTM!Barrel export 패턴이 깔끔하게 적용되었습니다.
CreateGroupLinkButton,EmptyBox,GroupLinkButton컴포넌트를 일관된 방식으로 export하고 있습니다.src/shared/ui/NameChip/index.styles.ts (1)
10-36: LGTM!테마 토큰과
TextVariant헬퍼를 활용한 깔끔한 구현입니다.$variant와$size에 따른 스타일 분기가 명확합니다.기본 variant(
inherit반환)가 의도된 동작인지만 확인해 주세요. 부모 스타일을 상속받는 유연한 사용을 위한 것이라면 문제없습니다.src/pages/memberSetup/MemberSetupPage.tsx (2)
30-43: 에러 핸들러 구현이 적절합니다.403 에러 핸들링과 BoundaryError 사용이 잘 구현되어 있습니다. NOTE 주석에서 실제 API가 500을 반환한다고 언급하고 있는데, API가 수정되면 해당 주석을 업데이트하거나 제거해 주세요.
78-80: LGTM!
generatePath를 사용한 동적 라우트 생성이 올바르게 구현되었습니다.src/shared/ui/NameChip/index.stories.tsx (1)
1-76: LGTM!Storybook 스토리 파일이 잘 구성되어 있습니다. Showcase 스토리가 모든 variant와 size 조합을 테이블로 보여주어 컴포넌트 문서화에 유용합니다.
src/shared/ui/NameChip/index.tsx (1)
1-18: LGTM!NameChip 컴포넌트가 깔끔하게 구현되었습니다. styled-components의 transient props 패턴(
$variant,$size)을 올바르게 사용하고 있으며, 타입 정의와 기본값 설정이 적절합니다.src/pages/selectGroup/ui/GroupLinkButton.tsx (2)
9-12:group.id를groupToken으로 사용하는 것이 의도된 것인지 확인해 주세요.
Group타입에는id와groupName필드가 있지만, 라우트 파라미터는groupToken을 기대합니다.id를groupToken으로 별칭 지정하는 것이 의도된 것이라면,Group타입에token필드를 추가하거나 주석을 추가하여 이 매핑이 명확하도록 하는 것을 고려해 주세요.
16-22: LGTM!
members배열에 대한 optional chaining과 조건부 variant 로직이 적절합니다.src/pages/confirmStep/ConfirmStepPage.tsx (1)
9-9: LGTM!Bill → Expense 용어 통일 작업의 일환으로 타입명 변경이 올바르게 적용되었습니다.
EditExpenseContext타입 정의와 일치합니다.Also applies to: 17-17
src/pages/selectGroup/SelectGroupPage.tsx (1)
55-64: 데이터 기반 렌더링 LGTM
CreateGroupLinkButton과 조건부GroupLinkButton/EmptyBox렌더링 구조가 깔끔합니다. 기존 상태 기반 UI에서 데이터 기반으로 잘 리팩터링되었습니다.src/app/App.tsx (1)
16-17:useApiError호출 방식으로 인한 불필요한 의존성 재생성 제거
useApiError의handleError는 이미useCallback으로 메모이제이션되어 있습니다. 다만, 매 렌더링마다 빈 객체{}를 새로 전달하면handlers의존성이 변경되어handleError참조가 계속 갱신됩니다.
useApiError를 컴포넌트 외부에서 호출하거나useMemo로 감싸서 인수를 안정화하세요:const errorHandlers = useMemo(() => ({}), []); const { handleError: handleQueryError } = useApiError(errorHandlers); const { handleError: handleMutationError } = useApiError(errorHandlers);또는
errorHandlers인수를 생략하는 경우 기본값을 안정화하는 방안을 고려하세요.src/shared/ui/NameChip/index.type.ts (1)
1-4: 타입/상수 조합 패턴이 깔끔합니다.
as const+ union 타입 추출로 variant/size가 타입 안정적으로 고정되어서, 컴포넌트/스타일/스토리에서 오타 방지에 도움이 됩니다.src/mocks/handlers/group.ts (1)
5-66:dummyGroups의 멤버 필드/ID 타입이Member정의와 일치하는지 확인 필요.
dummyGroups: Group[]로 강타입을 준 건 좋은데,members[].id가number(1,2,3…)로 들어가 있어서Member.id가string인 설계라면 곧바로 타입/런타임 불일치가 납니다.src/entities/group/model/group.type.ts의Member인터페이스와 정합성만 한 번 확인해주세요.src/entities/group/api/group.ts (1)
9-12: mock 사용 흐름이면getGroupDetail도useMock전달을 고려해주세요.
현재 MSW handler 쪽은getIsMocked(request)로 passthrough를 결정하는데,getGroupDetail()에는useMock이 없어 로컬에서 실서버로 튈 가능성이 있어 보여요(의도라면 OK).export const getGroupDetail = async (groupToken: string): Promise<Group> => { const response = await axiosInstance.get('/group', { params: { groupToken }, + useMock: true, }); return response.data; };Also applies to: 14-19
src/entities/auth/lib/checkAuth.ts (1)
9-28: 가드 로직을 “토큰 존재”에서 “유저 조회”로 바꾼 방향은 좋습니다.
다만ensureQueryData옵션(gcTime포함)과redirect()반환 패턴이 현재 사용 중인 TanStack Query / react-router 버전 조합에서 의도대로 동작하는지(특히 loader/guard 호출 컨텍스트)만 확인 부탁드립니다.src/shared/config/route.ts (1)
7-11: 라우트 키 rename이 완벽하게 완료되었습니다.제거된
ROUTE.createBill,ROUTE.billDetail,ROUTE.billDetailCharacterShare참조는 남아있지 않으며, 구 경로인/create-bill,/bill-detail하드코딩도 확인되지 않습니다. 모든 ROUTE 참조가 새로운 키(expenseDetail,createExpense,characterShare등)로 일관되게 마이그레이션되었으므로 별도 조치 불필요합니다.src/features/expense-management/lib/useAddExpenseFormArray.ts (1)
73-78: LGTM!
groupInfo를groupData로 매핑하여 반환하는 방식은 기존 API와의 하위 호환성을 유지하면서 loader 기반 데이터로 전환한 좋은 접근입니다.src/app/Router.tsx (1)
97-107:expenseDetail및characterShare라우트가 protected 블록 외부에 위치TODO 주석에 명시된 대로, 현재 이 라우트들은
checkAuth보호 없이groupTokenUrlLoader만 사용하고 있습니다. 의도된 동작인지 확인이 필요합니다. 인증이 필요한 페이지라면 protected 블록 내부로 이동해야 합니다.src/pages/CreateExpensePage/CreateExpensePage.tsx (1)
1-6: LGTM!Bill에서 Expense로의 명명 변경이 일관성 있게 적용되었습니다.
useFunnel제네릭 타입, funnel id, 그리고 컨텍스트 타입들이 모두 올바르게 업데이트되었습니다.Also applies to: 15-32
QueryClient 설정을 렌더 이후에 주입하던 방식을 제거 #10 (comment) 리뷰 반영
- 모듈 로드 시점 에러로 인한 앱 전체 크래시를 방지하기 위함 - #10 (comment) 리뷰 반영
|
✨ Storybook preview : https://6867dc9b2bbd1b3d76b92f4a-ifsdirkcpi.chromatic.com/ |
💻 작업 내용
📍 이건 localstorage에 토큰 저장하던 것들을 차차 수정해야 한다고 얘기 나눴던 기억이 있어서 변경한건데, 만약 localstorage 저장 방식을 유지해야 한다면 수정하겠습니다!
예전에 컴포넌트 정리했던 것에서 컴포넌트가 달라진 부분이 있는 것 같아서 이것도 UI 작업 하면서 수정하면 좋을 것 같습니당!!
📸 스크린샷
Summary by CodeRabbit
릴리스 노트
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.