Skip to content

정산 참여(join) 플로우 구현 + 프로필 컴포넌트 리팩토링#28

Merged
yoouyeon merged 9 commits intodevelopfrom
feat/MD-22
Apr 19, 2026
Merged

정산 참여(join) 플로우 구현 + 프로필 컴포넌트 리팩토링#28
yoouyeon merged 9 commits intodevelopfrom
feat/MD-22

Conversation

@yoouyeon
Copy link
Copy Markdown
Contributor

@yoouyeon yoouyeon commented Apr 3, 2026

변경사항이 많아서... 커밋 단위로 보시면 좀 더 리뷰하기 좋으실 것 같아요!

💻 작업 내용

공유 링크로 진입한 참여자가 프로필을 선택하고 정산 상세 페이지로 이동하는 플로우를 구현했습니다.

진입 플로우

/join/:groupToken 진입
→ (미로그인) 로그인 페이지로 이동
→ (이미 프로필 선택) /expense-detail/:groupToken으로 이동
→ (프로필 미선택) 프로필 선택 UI 표시
↓ 프로필 선택 후 확인
/expense-detail/:groupToken 진입

주요 변경사항

정산 참여 페이지 (pages/join)

  • joinLoader: 인증 확인 → 기존 프로필 선택 여부 확인 → 프로필 목록 조회
  • JoinPage: 프로필 그리드 표시

정산 상세 로더 교체 (pages/expenseDetail/loader.ts)

  • 기존 groupTokenUrlLoaderexpenseDetailLoader로 교체
  • 프로필 미선택 시 /join으로 리다이렉트

useAssignMember 훅 (features/join)

  • 프로필 할당 성공 후 profiles 캐시를 제거해 loader가 최신 데이터를 fetch하도록 함

Profile / ProfileImage 컴포넌트 통합

  • MemberProfileProfile (size variant S/m/L 추가, ProfileImage 재사용)
  • MemberProfileImageProfileImage (size를 '36' | '40' | '48' | '68'로 변경)

📸 스크린샷

기본 + 프로필 클릭 스크롤 활성화 + 프로필 클릭 전
Screen_Shot 2026-04-03 15 17 22 Screen_Shot 2026-04-03 15 16 05

Summary by CodeRabbit

  • New Features

    • 정산 참여(가입) 페이지 및 라우트 추가 — 프로필 선택 후 멤버 할당 및 자동 이동 흐름 제공
    • 지출 상세 페이지 접근 전 인증·가입 상태를 검사하는 로더 추가(필요시 리다이렉트)
  • Refactor

    • 프로필 UI 통합: 새 Profile/ ProfileImage 컴포넌트로 이미지 크기·상태(선택/삭제/체크/비활성) 개선
    • 멤버 API 사용 흐름 정리(프로필 조회·멤버 할당 등)
  • Chores

    • 개발용 목(Mock) 핸들러 및 Storybook 파일 추가/정리 (스토리 이동/삭제 및 신규 등록)

- 피그마 디자인 시스템 명칭에 맞게 컴포넌트를 리네이밍
- Profile 컴포넌트에 피그마 기준 size variant(S/m/L)를 추가
- Profile이 ProfileImage를 내부적으로 재사용하도록 통합
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 3, 2026

📝 관련 이슈

MD-22

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9efff222-a49d-473d-b4c6-2c53389a79b9

📥 Commits

Reviewing files that changed from the base of the PR and between 691a8c3 and 7c131c9.

📒 Files selected for processing (3)
  • src/app/Router.tsx
  • src/features/payment-management/ui/PaymentAlert/index.tsx
  • src/shared/config/route.ts
✅ Files skipped from review due to trivial changes (1)
  • src/shared/config/route.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/Router.tsx

📝 Walkthrough

Walkthrough

이 PR은 정산 참여(Join) 플로우와 관련 API/로더/MSW 핸들러를 추가하고, 기존 MemberProfile/MemberProfileImage를 Profile/ProfileImage로 교체하며 라우트와 스토리북을 갱신합니다.

Changes

Cohort / File(s) Summary
라우팅 & 라우터
src/app/Router.tsx, src/shared/config/route.ts
ROUTE.join 추가 및 ROUTE.join용 lazy route 등록. expenseDetail 라우트 로더를 expenseDetailLoader로 교체.
인증 API & MSW
src/entities/auth/api/auth.ts, src/mocks/handlers/auth.ts
클라이언트와 MSW에서 GET /user/infoGET /user로 엔드포인트 경로 변경.
그룹 API·로더·MSW
src/entities/group/api/group.ts, src/mocks/handlers/group.ts, src/pages/expenseDetail/loader.ts, src/pages/expenseDetail/ui/ExpenseTimeHeader/index.stories.tsx
getGroupHeader 시그니처를 groupTokensettlementCode로 변경하고 엔드포인트를 /groups/:settlementCode/header로 전환. MSW에 멤버 조회(GET /groups/:settlementCode/members)·할당(POST /groups/:settlementCode/members/assign) 및 더미 멤버 데이터 추가.
멤버 API·타입
src/entities/member/api/assignMember.ts, src/entities/member/api/getProfiles.ts, src/entities/member/model/member.type.ts
assignMembergetProfiles 추가. MemberProfile/MemberProfileData 타입 추가.
Join 페이지 & 로더
src/pages/join/JoinPage.tsx, src/pages/join/JoinPage.styles.ts, src/pages/join/loader.ts, src/pages/join/index.ts
JoinPage 컴포넌트·스타일·로더·재수출 추가: 프로필 선택 UI, 할당 뮤테이션 호출, 인증·프로필·그룹 검증 및 적절한 리디렉션 로직 포함.
뮤테이션 훅
src/features/join/api/useAssignMember.ts
useAssignMember(groupToken) 훅 추가: assignMember 호출 및 성공 시 ['profiles', groupToken] 쿼리 제거.
프로필 UI(신규)
src/shared/ui/Profile/index.tsx, src/shared/ui/Profile/index.style.ts, src/shared/ui/Profile/index.stories.ts
Profile 컴포넌트 추가(크기·타입 옵션, 삭제/체크/비활성 상태) 및 스토리 추가.
프로필 이미지(신규)
src/shared/ui/ProfileImage/index.tsx, src/shared/ui/ProfileImage/index.styles.ts, src/shared/ui/ProfileImage/index.stories.ts
숫자 기반 크기(`'36'
컴포넌트 마이그레이션
src/features/expense-management/ui/MemberExpenses/index.tsx, src/pages/expenseDetail/ui/ExpenseMemberItem/index.tsx, src/features/user-profile/ui/MyProfile/index.tsx, src/pages/memberSetup/ui/AddMember/index.tsx, src/features/payment-management/ui/PaymentAlert/index.tsx
기존 MemberProfile/MemberProfileImage 사용처를 Profile/ProfileImage로 교체하고 props 매핑·사이즈 조정 수행.
삭제된 MemberProfile 관련 파일
src/shared/ui/MemberProfile/index.tsx, src/shared/ui/MemberProfile/index.style.ts, src/shared/ui/MemberProfile/index.stories.ts
기존 MemberProfile 컴포넌트, 스타일, 스토리 삭제.
삭제된 MemberProfileImage 관련 파일
src/shared/ui/MemberProfileImage/index.tsx, src/shared/ui/MemberProfileImage/index.stories.ts
기존 MemberProfileImage 컴포넌트 및 스토리 삭제.
스토리북·MSW 테스트 업데이트
src/pages/expenseDetail/ui/ExpenseTimeHeader/index.stories.tsx, src/mocks/handlers/*
스토리북용 MSW 경로를 파라미터화된 /groups/:settlementCode/header로 변경 및 테스트용 핸들러 확장.

Possibly related PRs

Poem

🐰 깡총깡총 모여라, 새 프로필들
클릭하면 선택, 선택하면 찰칵
오래된 컴포넌트는 안녕, 새 얼굴 반가워
라우트는 길을 열고 데이터는 흐른다
토끼가 박수, 정산 한 걸음 더 ✨

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

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/MD-22

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.

Copy link
Copy Markdown

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
src/features/user-profile/ui/MyProfile/index.tsx (1)

18-28: ⚠️ Potential issue | 🟡 Minor

Optional chaining 사용이 일관되지 않아 런타임 오류 가능성이 있습니다.

Line 18에서 profile?.profileImageUrl은 optional chaining을 사용하지만, Line 20의 profile.name과 Line 27의 profile.email은 사용하지 않습니다. profileundefined일 경우 컴포넌트가 크래시될 수 있습니다.

🛡️ 일관된 null 체크 적용 제안
   return (
     <S.ProfileContainer>
       <ProfileImage size="36" src={profile?.profileImageUrl} />
       <Flex direction="column" flex={1} gap={4}>
-        <Text variant="body1Sb">{profile.name}</Text>
+        <Text variant="body1Sb">{profile?.name}</Text>
         {/* TODO: 디자인 시스템 정비 후 다시 디자인 확인이 필요합니다 (Opacity를 계속 쓰는지?) */}
         <Text
           variant="body2R"
           color="semantic.text.default"
           style={{ opacity: 0.5 }}
         >
-          {profile.email}
+          {profile?.email}
         </Text>
       </Flex>

또는 컴포넌트 상단에서 early return 처리:

if (!profile) return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/user-profile/ui/MyProfile/index.tsx` around lines 18 - 28, The
JSX uses profile?.profileImageUrl but accesses profile.name and profile.email
without null checks; update the MyProfile component to consistently guard
against undefined profile—either add optional chaining (e.g., profile?.name and
profile?.email) where used (ProfileImage, Text elements) or perform an early
return at the top of the MyProfile render (e.g., if (!profile) return null) so
that ProfileImage, the Text for name, and the Text for email cannot read
properties from undefined.
🧹 Nitpick comments (7)
src/entities/group/api/group.ts (1)

47-49: 개발 환경에서 mock 요청이 동작하지 않을 수 있습니다.

다른 API 함수들(getGroupList, assignMember, getUserInfo 등)은 useMock: true 옵션을 사용하는데, getGroupHeader에는 누락되어 있습니다. mock 핸들러(/api/v1/groups/:settlementCode/header)가 존재하므로 개발 환경에서의 일관성을 위해 추가를 검토해 주세요.

♻️ useMock 옵션 추가 제안
 export const getGroupHeader = (
   settlementCode: string
 ): Promise<GroupHeaderResponse> => {
   return axiosInstance
-    .get(`/groups/${settlementCode}/header`)
+    .get(`/groups/${settlementCode}/header`, { useMock: true })
     .then((res) => res.data);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/group/api/group.ts` around lines 47 - 49, getGroupHeader is
missing the useMock option so dev mock handler
(/api/v1/groups/:settlementCode/header) won't be used; update the axios call in
getGroupHeader to include the same useMock: true option used by getGroupList,
assignMember, and getUserInfo so the request routes to the mock handler in
development and maintains consistency with other API functions.
src/shared/ui/ProfileImage/index.tsx (1)

4-10: img 대체 텍스트를 받을 수 있게 열어두는 게 좋습니다.

Line 10의 <img>alt 전달 경로가 없어 접근성 제어가 제한됩니다. alt prop(기본값 '')을 추가해 장식/의미 이미지를 호출부에서 선택 가능하게 해주세요.

♿ 제안 패치
 interface ProfileImageProps {
   src?: string;
   size: '36' | '40' | '48' | '68';
+  alt?: string;
 }
 
-function ProfileImage({ src, size }: ProfileImageProps) {
-  return <S.Image src={src || defaultProfileImg} $size={size} />;
+function ProfileImage({ src, size, alt = '' }: ProfileImageProps) {
+  return <S.Image src={src || defaultProfileImg} alt={alt} $size={size} />;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/ProfileImage/index.tsx` around lines 4 - 10, Add an optional
alt prop to the ProfileImage API and forward it to the rendered image: update
ProfileImageProps to include alt?: string (default to '' in the component),
accept alt in the ProfileImage function signature, and pass alt={alt ?? ''} into
S.Image (which currently receives src and $size) so callers can choose
decorative or meaningful alt text; reference ProfileImage, ProfileImageProps,
S.Image, and defaultProfileImg when making the change.
src/mocks/handlers/group.ts (1)

191-196: MSW 라우트 파라미터 검증은 불필요합니다.

MSW에서 /api/v1/groups/:settlementCode/members 형태의 경로 패턴을 사용하면, params.settlementCode는 항상 존재합니다 (빈 문자열일 수는 있어도 undefined가 아님). 이 검증 로직은 도달할 수 없는 코드입니다.

🔧 불필요한 검증 제거
   http.get('/api/v1/groups/:settlementCode/members', ({ request, params }) => {
     if (!getIsMocked(request)) return passthrough();
 
-    const { settlementCode } = params;
-
-    if (!settlementCode) {
-      return HttpResponse.json(
-        { error: 'settlementCode is required' },
-        { status: 400 }
-      );
-    }
-
     return HttpResponse.json({ members: dummyMemberList });
   }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mocks/handlers/group.ts` around lines 191 - 196, Remove the unreachable
MSW route parameter validation that checks if (!settlementCode) and returns
HttpResponse.json(...); specifically delete the conditional block that inspects
params.settlementCode and returns a 400, and rely on params.settlementCode
directly (it will always be defined by the
/api/v1/groups/:settlementCode/members route); ensure no other logic expects
that early 400 return.
src/pages/join/JoinPage.tsx (1)

31-33: 디버깅용 console.log는 프로덕션 전에 제거해 주세요.

console.log('프로필 선택 성공')은 개발 중 유용하지만, 프로덕션 빌드에서는 제거하거나 적절한 로깅 유틸리티로 대체하는 것이 좋습니다.

🧹 console.log 제거
     assignMember(selectedId, {
       onSuccess: () => {
-        console.log('프로필 선택 성공');
         navigate(`/expense-detail/${groupToken}`);
       },
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/join/JoinPage.tsx` around lines 31 - 33, Remove the debugging
console.log from the onSuccess handler in JoinPage.tsx: inside the onSuccess
callback (where navigate(`/expense-detail/${groupToken}`) is called), delete
console.log('프로필 선택 성공') or replace it with a call to the app's logging utility
if persistent logs are required; ensure the onSuccess remains calling navigate
with groupToken and no stray console statements remain.
src/shared/ui/Profile/index.tsx (1)

47-59: Flex 내부의 key prop은 불필요합니다.

key={id}가 컴포넌트 내부의 Flex에 전달되고 있지만, React의 key는 리스트 렌더링 시 부모 컴포넌트에서 <Profile key={...} />와 같이 전달해야 합니다. 컴포넌트 내부에서 key를 사용하는 것은 의미가 없습니다.

🔧 불필요한 key prop 제거
   return (
     <Flex
-      key={id}
       gap={4}
       direction="column"
       alignItems="center"
       width="fit-content"
       py={8}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/Profile/index.tsx` around lines 47 - 59, Remove the unnecessary
key prop passed into the Flex element inside the Profile component: the Flex JSX
has key={id} which is meaningless inside the component body; instead ensure any
list rendering of Profile supplies the key at the parent level. Edit the Profile
component to delete key={id} from the <Flex ...> element (leave id and
handleClick usage intact for logic/props) so only external callers supply keys
when mapping arrays.
src/shared/ui/Profile/index.style.ts (1)

10-17: DeleteButton에 기본 버튼 스타일 리셋이 누락되었습니다.

<button> 요소는 브라우저 기본 스타일(배경색, 테두리 등)을 가지고 있습니다. 일관된 UI를 위해 리셋 스타일을 추가하는 것이 좋습니다.

🔧 버튼 리셋 스타일 추가
 export const DeleteButton = styled.button`
   position: absolute; // 자식 요소
   width: fit-content;
   height: fit-content;
+  padding: 0;
+  background: transparent;
+  border: none;
+  cursor: pointer;
   &:hover {
     filter: brightness(0.6);
   }
 `;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/Profile/index.style.ts` around lines 10 - 17, The DeleteButton
styled component is missing browser default reset styles; update the
DeleteButton (styled.button) to reset native button appearance by explicitly
setting background: transparent (or none), border: none, padding: 0, margin: 0,
outline: none (or outline: 0), appearance: none (-webkit-appearance: none) and
cursor: pointer (and optionally line-height: 1) so the component renders
consistently across browsers while keeping the existing position: absolute and
hover filter behavior.
src/pages/join/JoinPage.styles.ts (1)

36-44: 그라데이션 색상이 하드코딩되어 있습니다.

rgba(255, 255, 255, 0)white가 직접 사용되고 있어 다크 모드 지원 시 문제가 될 수 있습니다. 당장은 문제없지만, 향후 테마 기반 배경색을 사용하는 것을 고려해 주세요.

🎨 테마 기반 색상 사용 예시
 export const GradientOverlay = styled.div`
   position: absolute;
   bottom: 0;
   left: 0;
   right: 0;
   height: 200px;
-  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), white);
+  background: linear-gradient(
+    to bottom,
+    ${({ theme }) => theme.color.background.primary}00,
+    ${({ theme }) => theme.color.background.primary}
+  );
   pointer-events: none;
 `;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/join/JoinPage.styles.ts` around lines 36 - 44, GradientOverlay
currently hardcodes colors (rgba(255,255,255,0) and white) which breaks
theme/dark-mode; update the styled component GradientOverlay to pull colors from
the theme (e.g. theme.colors.background or theme.palette.background) instead of
literal values and derive a transparent variant for the top stop (use your theme
helper/utility or CSS rgba/transparentize with the theme color and alpha 0),
preserving the linear-gradient direction and height; this ensures the gradient
respects dark mode and future theme changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/expenseDetail/loader.ts`:
- Line 33: Prettier is flagging the line that assigns myProfile using
profiles.find; fix by applying the project's Prettier formatting to this file
(or run the formatter/IDE auto-format) so the expression const myProfile =
profiles.find((profile) => profile.userId === user.id) ?? null; is reformatted
to match lint rules (or split into multiple lines if configured) and then commit
the formatted change; ensure the symbol myProfile and the profiles.find(...)
expression remain functionally identical after formatting.
- Around line 42-50: The catch currently converts every Axios 401 into a
BoundaryError which breaks the login-redirect flow; update the catch in
loader.ts to distinguish authentication vs authorization: if isAxiosError(error)
and error.response?.status === 401, inspect the request context (e.g.,
error.response.config.url or the caller like getUserInfo) and rethrow the
original error for auth-related endpoints (so the login redirect can run), but
convert only permission-check responses (or non-auth endpoints) into the
BoundaryError; use symbols isAxiosError, BoundaryError and getUserInfo to locate
the logic to implement this conditional behavior.
- Line 26: The redirect call building the returnUrl
(redirect(`/login?returnUrl=/expense-detail/${groupToken}`)) can break if
groupToken contains special characters; update the code in the loader where
redirect is invoked to URL-encode the returnUrl (e.g., wrap the path or the
groupToken with encodeURIComponent) so the query parameter is safe when
redirecting to login.

In `@src/pages/join/loader.ts`:
- Line 22: The LoginPage currently ignores the returnUrl query added by the
loader's redirect (e.g., `/login?returnUrl=/join/${groupToken}`); update the
LoginPage component to read the returnUrl via useSearchParams, and after a
successful login (e.g., in the existing handleSubmit or onLoginSuccess flow)
navigate to that returnUrl instead of always redirecting to home. Concretely:
call const [searchParams] = useSearchParams() to get
searchParams.get('returnUrl'), validate/sanitize the value, and then use
useNavigate() or the existing navigation helper in the success callback of
LoginPage's login handler to route to the returnUrl (fallback to '/' if missing
or invalid). Ensure you reference LoginPage, useSearchParams, useNavigate and
the login success handler when applying the change.

---

Outside diff comments:
In `@src/features/user-profile/ui/MyProfile/index.tsx`:
- Around line 18-28: The JSX uses profile?.profileImageUrl but accesses
profile.name and profile.email without null checks; update the MyProfile
component to consistently guard against undefined profile—either add optional
chaining (e.g., profile?.name and profile?.email) where used (ProfileImage, Text
elements) or perform an early return at the top of the MyProfile render (e.g.,
if (!profile) return null) so that ProfileImage, the Text for name, and the Text
for email cannot read properties from undefined.

---

Nitpick comments:
In `@src/entities/group/api/group.ts`:
- Around line 47-49: getGroupHeader is missing the useMock option so dev mock
handler (/api/v1/groups/:settlementCode/header) won't be used; update the axios
call in getGroupHeader to include the same useMock: true option used by
getGroupList, assignMember, and getUserInfo so the request routes to the mock
handler in development and maintains consistency with other API functions.

In `@src/mocks/handlers/group.ts`:
- Around line 191-196: Remove the unreachable MSW route parameter validation
that checks if (!settlementCode) and returns HttpResponse.json(...);
specifically delete the conditional block that inspects params.settlementCode
and returns a 400, and rely on params.settlementCode directly (it will always be
defined by the /api/v1/groups/:settlementCode/members route); ensure no other
logic expects that early 400 return.

In `@src/pages/join/JoinPage.styles.ts`:
- Around line 36-44: GradientOverlay currently hardcodes colors
(rgba(255,255,255,0) and white) which breaks theme/dark-mode; update the styled
component GradientOverlay to pull colors from the theme (e.g.
theme.colors.background or theme.palette.background) instead of literal values
and derive a transparent variant for the top stop (use your theme helper/utility
or CSS rgba/transparentize with the theme color and alpha 0), preserving the
linear-gradient direction and height; this ensures the gradient respects dark
mode and future theme changes.

In `@src/pages/join/JoinPage.tsx`:
- Around line 31-33: Remove the debugging console.log from the onSuccess handler
in JoinPage.tsx: inside the onSuccess callback (where
navigate(`/expense-detail/${groupToken}`) is called), delete console.log('프로필 선택
성공') or replace it with a call to the app's logging utility if persistent logs
are required; ensure the onSuccess remains calling navigate with groupToken and
no stray console statements remain.

In `@src/shared/ui/Profile/index.style.ts`:
- Around line 10-17: The DeleteButton styled component is missing browser
default reset styles; update the DeleteButton (styled.button) to reset native
button appearance by explicitly setting background: transparent (or none),
border: none, padding: 0, margin: 0, outline: none (or outline: 0), appearance:
none (-webkit-appearance: none) and cursor: pointer (and optionally line-height:
1) so the component renders consistently across browsers while keeping the
existing position: absolute and hover filter behavior.

In `@src/shared/ui/Profile/index.tsx`:
- Around line 47-59: Remove the unnecessary key prop passed into the Flex
element inside the Profile component: the Flex JSX has key={id} which is
meaningless inside the component body; instead ensure any list rendering of
Profile supplies the key at the parent level. Edit the Profile component to
delete key={id} from the <Flex ...> element (leave id and handleClick usage
intact for logic/props) so only external callers supply keys when mapping
arrays.

In `@src/shared/ui/ProfileImage/index.tsx`:
- Around line 4-10: Add an optional alt prop to the ProfileImage API and forward
it to the rendered image: update ProfileImageProps to include alt?: string
(default to '' in the component), accept alt in the ProfileImage function
signature, and pass alt={alt ?? ''} into S.Image (which currently receives src
and $size) so callers can choose decorative or meaningful alt text; reference
ProfileImage, ProfileImageProps, S.Image, and defaultProfileImg when making the
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e0bb6217-0247-4ee3-bc8e-31e2ba3c66c9

📥 Commits

Reviewing files that changed from the base of the PR and between b58628b and e65bb7c.

📒 Files selected for processing (30)
  • src/app/Router.tsx
  • src/entities/auth/api/auth.ts
  • src/entities/group/api/group.ts
  • src/entities/member/api/assignMember.ts
  • src/entities/member/api/getProfiles.ts
  • src/entities/member/model/member.type.ts
  • src/features/expense-management/ui/MemberExpenses/index.tsx
  • src/features/join/api/useAssignMember.ts
  • src/features/user-profile/ui/MyProfile/index.tsx
  • src/mocks/handlers/auth.ts
  • src/mocks/handlers/group.ts
  • src/pages/expenseDetail/loader.ts
  • src/pages/expenseDetail/ui/ExpenseMemberItem/index.tsx
  • src/pages/join/JoinPage.styles.ts
  • src/pages/join/JoinPage.tsx
  • src/pages/join/index.ts
  • src/pages/join/loader.ts
  • src/pages/memberSetup/ui/AddMember/index.tsx
  • src/shared/config/route.ts
  • src/shared/ui/MemberProfile/index.stories.ts
  • src/shared/ui/MemberProfile/index.style.ts
  • src/shared/ui/MemberProfile/index.tsx
  • src/shared/ui/MemberProfileImage/index.stories.ts
  • src/shared/ui/MemberProfileImage/index.tsx
  • src/shared/ui/Profile/index.stories.ts
  • src/shared/ui/Profile/index.style.ts
  • src/shared/ui/Profile/index.tsx
  • src/shared/ui/ProfileImage/index.stories.ts
  • src/shared/ui/ProfileImage/index.styles.ts
  • src/shared/ui/ProfileImage/index.tsx
💤 Files with no reviewable changes (5)
  • src/shared/ui/MemberProfile/index.tsx
  • src/shared/ui/MemberProfile/index.style.ts
  • src/shared/ui/MemberProfileImage/index.stories.ts
  • src/shared/ui/MemberProfile/index.stories.ts
  • src/shared/ui/MemberProfileImage/index.tsx

Comment thread src/pages/expenseDetail/loader.ts Outdated
Comment thread src/pages/expenseDetail/loader.ts Outdated
Comment thread src/pages/expenseDetail/loader.ts
yoouyeon added 4 commits April 3, 2026 15:58
공유 링크로 진입한 참여자 프로필 선택 → 정산 상세 페이지로 이동하는 플로우 구현

- joinLoader : 인증 확인 → 프로필 선택 여부 확인 → 프로필 목록 반환
- expenseDetailLoader : 정산 상세 페이지 진입 전 프로필 미선택 시 Join 페이지로 리다이렉트
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (3)
src/mocks/handlers/group.ts (2)

209-209: 디버그용 console.log 제거를 고려해주세요.

Mock 핸들러에 남겨진 로그는 개발 중 콘솔을 어지럽힐 수 있습니다. 필요하다면 주석 처리하거나 제거하는 것이 좋습니다.

♻️ 제거 제안
-     console.log(`settlementCode: ${settlementCode}, memberId: ${memberId}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mocks/handlers/group.ts` at line 209, Remove the leftover debug
console.log in the mock handler: locate and delete or comment out the line
console.log(`settlementCode: ${settlementCode}, memberId: ${memberId}`) in
src/mocks/handlers/group.ts (inside the group mock handler function) so the mock
no longer emits development logs to the console.

189-196: settlementCode 유효성 검증이 불필요합니다.

MSW의 path parameter는 URL에서 추출되므로 /api/v1/groups/:settlementCode/members 경로로 요청이 도달했다면 settlementCode는 항상 존재합니다. 이 검증은 절대 400을 반환하지 않습니다.

♻️ 불필요한 검증 제거 제안
  http.get('/api/v1/groups/:settlementCode/members', ({ request, params }) => {
    if (!getIsMocked(request)) return passthrough();

-   const { settlementCode } = params;
-
-   if (!settlementCode) {
-     return HttpResponse.json(
-       { error: 'settlementCode is required' },
-       { status: 400 }
-     );
-   }

    return HttpResponse.json({ members: dummyMemberList });
  }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mocks/handlers/group.ts` around lines 189 - 196, The guard that checks
params.settlementCode and returns HttpResponse.json({ error: 'settlementCode is
required' }, { status: 400 }) is unnecessary because MSW path params guarantee
settlementCode for the route; remove the if-block that references settlementCode
and the associated HttpResponse.json error return so the handler proceeds using
params.settlementCode directly (look for the variables settlementCode and the
HttpResponse.json call in the handler to delete).
src/app/Router.tsx (1)

123-127: Join 라우트 배치가 의도적인지 확인이 필요합니다.

ROUTE.join 라우트가 protected 라우트 외부에 배치되어 있습니다. joinLoader가 자체적으로 인증을 처리하므로 문제는 없지만, expenseDetail과 함께 별도 그룹으로 묶거나 주석을 추가하면 가독성이 향상될 수 있습니다.

💡 라우트 그룹화 제안
        ],
      },
-     // TODO : 로그인 기능으로 변경될 예정
+     // 자체 인증 로직을 가진 라우트들 (loader에서 auth 처리)
      {
        path: ROUTE.join,
        element: <LazyJoinPage />,
        loader: joinLoader,
      },
      {
        path: ROUTE.expenseDetail,
        element: <LazyExpenseDetail />,
        loader: expenseDetailLoader,
      },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/Router.tsx` around lines 123 - 127, 현재 ROUTE.join (element:
LazyJoinPage, loader: joinLoader) 가 보호된 라우트 블록 밖에 있어 배치 의도를 명확히 할 필요가 있습니다;
joinLoader가 자체 인증을 처리하므로 동작상 문제는 없지만 가독성을 위해 join 라우트를 expenseDetail 등과 함께 별도
그룹으로 묶거나(예: 비인증/인증 분리 그룹) 해당 라우트 위에 간단한 주석을 추가해 의도를 명시하세요. ROUTE.join,
LazyJoinPage, joinLoader, expenseDetail 식별자를 찾아 둘을 같은 그룹으로 옮기거나 주석으로 "joinLoader
handles auth" 같은 문구를 추가하면 됩니다.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/join/loader.ts`:
- Line 22: The redirect call uses an unencoded returnUrl which can break if
groupToken contains special characters; update the redirect to URL-encode the
returnUrl (e.g., call encodeURIComponent on `/join/${groupToken}`) before
passing it to redirect so the query param is safe; locate the redirect usage
involving user and groupToken in loader.ts and apply encoding consistently
(matching how expenseDetailLoader handles returnUrl).
- Around line 16-22: The call to queryClient.ensureQueryData with getUserInfo
can throw a 401 which currently bubbles to the error boundary; wrap that call in
a try-catch (similar to expenseDetailLoader) and detect authentication failures
(HTTP 401 or auth-specific error) to perform
redirect(`/login?returnUrl=/join/${groupToken}`) instead of letting the error
propagate; ensure you only rethrow non-401 errors so other failures still
surface.

---

Nitpick comments:
In `@src/app/Router.tsx`:
- Around line 123-127: 현재 ROUTE.join (element: LazyJoinPage, loader: joinLoader)
가 보호된 라우트 블록 밖에 있어 배치 의도를 명확히 할 필요가 있습니다; joinLoader가 자체 인증을 처리하므로 동작상 문제는 없지만
가독성을 위해 join 라우트를 expenseDetail 등과 함께 별도 그룹으로 묶거나(예: 비인증/인증 분리 그룹) 해당 라우트 위에 간단한
주석을 추가해 의도를 명시하세요. ROUTE.join, LazyJoinPage, joinLoader, expenseDetail 식별자를 찾아
둘을 같은 그룹으로 옮기거나 주석으로 "joinLoader handles auth" 같은 문구를 추가하면 됩니다.

In `@src/mocks/handlers/group.ts`:
- Line 209: Remove the leftover debug console.log in the mock handler: locate
and delete or comment out the line console.log(`settlementCode:
${settlementCode}, memberId: ${memberId}`) in src/mocks/handlers/group.ts
(inside the group mock handler function) so the mock no longer emits development
logs to the console.
- Around line 189-196: The guard that checks params.settlementCode and returns
HttpResponse.json({ error: 'settlementCode is required' }, { status: 400 }) is
unnecessary because MSW path params guarantee settlementCode for the route;
remove the if-block that references settlementCode and the associated
HttpResponse.json error return so the handler proceeds using
params.settlementCode directly (look for the variables settlementCode and the
HttpResponse.json call in the handler to delete).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2f0c4e4f-b5bd-4022-a17f-8246593e6d02

📥 Commits

Reviewing files that changed from the base of the PR and between e65bb7c and 6e7e60a.

📒 Files selected for processing (16)
  • src/app/Router.tsx
  • src/entities/auth/api/auth.ts
  • src/entities/group/api/group.ts
  • src/entities/member/api/assignMember.ts
  • src/entities/member/api/getProfiles.ts
  • src/entities/member/model/member.type.ts
  • src/features/join/api/useAssignMember.ts
  • src/mocks/handlers/auth.ts
  • src/mocks/handlers/group.ts
  • src/pages/expenseDetail/loader.ts
  • src/pages/expenseDetail/ui/ExpenseTimeHeader/index.stories.tsx
  • src/pages/join/JoinPage.styles.ts
  • src/pages/join/JoinPage.tsx
  • src/pages/join/index.ts
  • src/pages/join/loader.ts
  • src/shared/config/route.ts
✅ Files skipped from review due to trivial changes (7)
  • src/entities/auth/api/auth.ts
  • src/pages/join/index.ts
  • src/pages/expenseDetail/ui/ExpenseTimeHeader/index.stories.tsx
  • src/shared/config/route.ts
  • src/entities/member/api/getProfiles.ts
  • src/pages/join/JoinPage.styles.ts
  • src/entities/member/model/member.type.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/entities/member/api/assignMember.ts
  • src/mocks/handlers/auth.ts
  • src/features/join/api/useAssignMember.ts
  • src/entities/group/api/group.ts
  • src/pages/join/JoinPage.tsx

Comment thread src/pages/join/loader.ts Outdated
Comment thread src/pages/join/loader.ts Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 3, 2026

@yoouyeon
Copy link
Copy Markdown
Contributor Author

yoouyeon commented Apr 3, 2026

@coderabbitai approve

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 3, 2026

✅ Actions performed

Comments resolved and changes approved.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 10, 2026

Deploying moddo-frontend with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7c131c9
Status: ✅  Deploy successful!
Preview URL: https://0a566ec7.moddo-frontend.pages.dev
Branch Preview URL: https://feat-md-22.moddo-frontend.pages.dev

View logs

yoouyeon added a commit that referenced this pull request Apr 11, 2026
401 응답 시 현재 경로를 redirectTo 쿼리 파라미터에 담아 로그인 페이지로 이동하도록 수정합니다.
또한 localStorage 기반 Authorization 헤더를 제거합니다 (쿠키 기반 인증으로 전환).

ref: #28 (comment)
@ongheong ongheong added ✨ Feature 신규 기능 추가 🔨 Refactor 코드 구조 개선 labels Apr 12, 2026
MemberProfileImage 제거, develop에서 사용되던 부분 수정 예정
@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown
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.

코드 확인했습니다! 정산 참여 플로우에서 사용자 상황별 라우팅과 프로필 선택 로직을 잘 구현해주셔서 감사합니다. 코멘트 몇개 남겼습니다 확인해주시면 감사하겠습니다~!

Comment thread src/pages/join/JoinPage.styles.ts
{members.map((member) => (
<MemberProfile
<Profile
key={member.id}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

여기서 key prop을 넘겨주는 이유가 무엇인가요? Profile 컴포넌트 내부에서 key는 id로 사용되는 것 같아 여쭤봅니당

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

여기서 key prop은 map을 이용해서 컴포넌트를 랜더링하고 있기 때문에 넣어 준 것입니다..!!!
https://react.dev/learn/rendering-lists

Comment on lines +9 to +11
'40': '2.5rem',
'48': '3rem',
'68': '4.25rem',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

기존에 sm/md/lg 시멘틱 키로 관리하던 걸 숫자 값으로 바꾸셨는데, 혹시 배경이 있을까요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

새로 정리된 프로필 컴포넌트의 사이즈 타입이 숫자 타입으로 되어 있더라고요!
일관성을 위해서 숫자 값을 담고 있는 문자열 리터럴로 바꾸었습니다!

https://www.figma.com/design/ZXka9a1g0bxfcNTaYVZZg2/MODDO2?node-id=61-54995&m=dev

Screen_Shot 2026-04-18 17 10 30

@yoouyeon yoouyeon merged commit 0d48d19 into develop Apr 19, 2026
3 checks passed
@yoouyeon yoouyeon deleted the feat/MD-22 branch April 19, 2026 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 신규 기능 추가 🔨 Refactor 코드 구조 개선

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants