Skip to content

캐릭터 도감 컴포넌트 추가#25

Merged
yoouyeon merged 8 commits intodevelopfrom
feat/MD-6
Feb 22, 2026
Merged

캐릭터 도감 컴포넌트 추가#25
yoouyeon merged 8 commits intodevelopfrom
feat/MD-6

Conversation

@yoouyeon
Copy link
Contributor

@yoouyeon yoouyeon commented Feb 19, 2026

#23 에 이어서 작업해서 base branch를 그쪽에 맞게 설정했습니다.
머지 전에 develop으로 변경하겠습니다!

💻 작업 내용

마이페이지에 캐릭터 도감 컴포넌트를 추가했습니다.
api 관련 로직도 목데이터 활용해서 추가했는데요.
아직 획득하지 않은 캐릭터 데이터를 어떻게 처리할 지 결정이 안된 상황이라 그 부분은 api 연결 시에 변경될 가능성이 있습니다!

📸 스크린샷

localhost_3000_my(iPhone 14 Pro Max)

Summary by CodeRabbit

  • 새로운 기능

    • 마이페이지에 캐릭터 도감 섹션이 추가되었습니다.
    • 캐릭터 그리드와 개별 캐릭터 카드가 도입되어 이미지, 이름, 잠금 상태 및 해제일을 표시합니다.
    • 잠금 해제된 캐릭터가 목록 상단에 정렬되어 표시됩니다.
    • 로딩 중 대체 표시와 인증 아이콘이 추가되었습니다.
  • 지역화

    • 사이트 기본 언어가 한국어(ko)로 변경되었습니다.

- 캐릭터 도감을 가져오는 API 함수 추가
- 캐릭터 도감 데이터 타입 정의 추가
- 캐릭터 도감에 대한 모킹 핸들러 추가
- 캐릭터 도감을 가져오는 API 훅 추가
- 캐릭터 도감 UI 추가
- 아이콘 추가
@yoouyeon yoouyeon self-assigned this Feb 19, 2026
@yoouyeon yoouyeon added the ✨ Feature 신규 기능 추가 label Feb 19, 2026
@github-actions
Copy link

github-actions bot commented Feb 19, 2026

📝 관련 이슈

MD-6

@github-actions github-actions bot requested a review from ongheong February 19, 2026 13:30
@yoouyeon
Copy link
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

캐릭터 도감 기능과 관련 API/타입, React Query 훅, MSW 목 핸들러, UI 컴포넌트(섹션/그리드/아이템) 및 스타일, Certified SVG 추가와 MyPage 통합을 수행하며 axios 요청 인터셉터의 모킹 헤더/베이스URL 주입을 개발 모드로 제한합니다.

Changes

Cohort / File(s) Summary
HTML 국제화
index.html
html 언어 속성을 "en"에서 "ko"로 변경; naver-site-verification 메타 태그 포맷 재배치(기능적 변경 없음).
캐릭터 엔티티: API & 타입
src/entities/character/api/character.ts, src/entities/character/model/character.type.ts
getCharacterCollection API 추가(공유 Axios 인스턴스 사용, mock 가능). CharacterItemData, CharacterItemsResponse 타입 추가.
캐릭터 훅(공개 재수출 포함)
src/features/character-management/api/useGetCharacterCollection.ts, src/features/character-management/api/index.ts
useGetCharacterCollection 훅 추가: useSuspenseQuery로 캐릭터 조회 및 isUnlocked 기준 정렬 반환. 인덱스에 명시적 재수출 추가.
캐릭터 UI — Grid / Item / Section
src/features/character-management/ui/...
CharacterGrid, CharacterItem, CharacterSection 컴포넌트 및 여러 styled-components 추가(잠금/해제 분기, 이미지/날짜 렌더링 포함). UI 인덱스에 재수출 추가.
MSW 목 핸들러 통합
src/mocks/handlers/character.ts, src/mocks/handlers.ts
/api/v1/character/collection GET mock 핸들러 추가 및 기존 handlers 배열에 병합. 샘플 페이로드 포함.
공유 SVG 아이콘
src/shared/assets/svgs/icon/Certified.tsx, src/shared/assets/svgs/icon/index.ts
Certified SVG 컴포넌트 추가 및 아이콘 인덱스에 Certified 명시적 내보내기 추가.
Axios 인터셉터 변경
src/shared/api/axios.ts
요청 인터셉터에서 X-Mock-Request 헤더 및 baseURL 모드 적용을 개발 모드(import.meta.env.MODE === 'development')로 제한하도록 변경.
페이지 통합
src/pages/my/MyPage.tsx
MyPage에 CharacterSection 컴포넌트 삽입.

Possibly related PRs

Suggested labels

🎨 Design

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 docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/MD-6

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.

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

✅ Actions performed

Full review triggered.

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

🧹 Nitpick comments (9)
src/shared/assets/svgs/icon/Certified.tsx (1)

1-27: SVG 시각적 표현(자물쇠)과 컴포넌트 이름(Certified) 불일치

SVG 경로를 보면 자물쇠(padlock) 형태이나, 컴포넌트 이름은 인증(Certified)을 의미합니다. 이후 잠금 아이콘이 필요한 개발자가 Lock, Padlock 등으로 검색할 때 이 아이콘을 찾지 못할 수 있습니다. 디자인 시스템에서의 의도적인 네이밍이라면 무방하지만, 그렇지 않다면 Lock 또는 Padlock으로 이름을 변경하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/assets/svgs/icon/Certified.tsx` around lines 1 - 27, The SVG
visually represents a padlock but the component is named SvgCertified; rename
the component and its export from SvgCertified to a clear lock name (e.g.,
SvgLock or SvgPadlock) and rename the file accordingly, update the default
export to match the new identifier, and then update all import sites (search for
SvgCertified) and any index/exports or storybook entries that reference it so
names remain consistent across the codebase.
src/entities/character/model/character.type.ts (1)

15-19: isUnlockedunlockedAt 간의 불변 조건을 타입으로 표현하면 더 안전합니다. (선택적 개선)

isUnlocked === false일 때 unlockedAt은 항상 null이어야 하는 암묵적 불변 조건이 있으나, 현재 타입은 이를 강제하지 않습니다. 판별 유니온(discriminated union)을 사용하면 타입 수준에서 두 필드 간의 관계를 명확하게 표현하고 컴파일 타임에 잘못된 조합을 방지할 수 있습니다.

♻️ 선택적 개선: 판별 유니온 적용
-export interface CharacterItemData extends CharacterData {
-  id: number;
-  isUnlocked: boolean;
-  unlockedAt: string | null;
-}
+export type CharacterItemData = CharacterData &
+  (
+    | { id: number; isUnlocked: true; unlockedAt: string }
+    | { id: number; isUnlocked: false; unlockedAt: null }
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/character/model/character.type.ts` around lines 15 - 19, The
CharacterItemData interface currently allows invalid combinations of isUnlocked
and unlockedAt; change it to a discriminated union so the relationship is
enforced: define CharacterItemData as either (isUnlocked: true; unlockedAt:
string; plus CharacterData) or (isUnlocked: false; unlockedAt: null; plus
CharacterData). Update any code that constructs or narrows CharacterItemData to
use the discriminant (isUnlocked) for type guards and ensure places that
previously assumed unlockedAt may be string now handle the null case when
isUnlocked is false.
src/features/character-management/api/useGetCharacterCollection.ts (1)

6-6: queryKey가 너무 범용적 — 다른 캐릭터 관련 쿼리와 충돌 가능

['characters'] 단일 키는 향후 캐릭터 관련 다른 쿼리와 캐시 키 충돌을 피하기 위해 더 구체적으로 명시하는 것이 TanStack Query 모범 사례입니다.

♻️ 제안: 더 구체적인 queryKey 사용
-    queryKey: ['characters'],
+    queryKey: ['characters', 'collection'],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/character-management/api/useGetCharacterCollection.ts` at line
6, The current queryKey used in useGetCharacterCollection is too generic
(['characters']) and can collide with other character queries; update the
queryKey in the useGetCharacterCollection hook to a more specific key such as
['characters', 'collection'] (or ['characters', 'collection', params] if there
are filter/owner params) so TanStack Query cache entries are namespaced
per-purpose and per-parameters; locate the queryKey assignment inside the
useGetCharacterCollection implementation and replace the array accordingly,
ensuring any dependent invalidations or usages (e.g.,
queryClient.invalidateQueries(['characters'])) are updated to the new key shape.
src/features/character-management/ui/CharacterSection/index.tsx (1)

14-16: 로딩 상태 텍스트 수정 + 선택적 로컬 ErrorBoundary 추가로 UX 개선

현재 코드는 throwOnError: true가 설정된 QueryClient와 전역 RouteErrorBoundary를 통해 API 오류를 처리하고 있으므로 에러가 처리되지 않는 문제는 없습니다. 다만 에러 발생 시 전체 페이지가 에러 상태로 표시되는 UX를 개선하기 위해 로컬 ErrorBoundary를 추가하는 것이 좋습니다.

또한 로딩 상태 텍스트를 "로딩 중"으로 수정하여 일관성을 유지해주세요.

✨ 제안: 로컬 ErrorBoundary와 텍스트 개선
 import { Suspense } from 'react';
+import { ErrorBoundary } from 'react-error-boundary';
 import Text from '@/shared/ui/Text';
 import CharacterGrid from '../CharacterGrid';
 import * as S from './index.styles';

 function CharacterSection() {
   return (
     <S.Container>
       <S.TitleWrapper>
         <Text color="semantic.text.strong" variant="title">
           캐릭터 도감
         </Text>
       </S.TitleWrapper>
+      <ErrorBoundary fallback={<S.CharacterGrid>캐릭터를 불러올 수 없습니다.</S.CharacterGrid>}>
-      <Suspense fallback={<S.CharacterGrid>로딩중</S.CharacterGrid>}>
+      <Suspense fallback={<S.CharacterGrid>로딩 중</S.CharacterGrid>}>
         <CharacterGrid />
       </Suspense>
+      </ErrorBoundary>
     </S.Container>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/character-management/ui/CharacterSection/index.tsx` around lines
14 - 16, Change the Suspense fallback text from "로딩중" to "로딩 중" for consistent
spacing and add a local ErrorBoundary around the CharacterGrid to improve UX on
component-level failures; create or reuse a small boundary component (e.g.,
LocalErrorBoundary or CharacterSectionErrorBoundary) and wrap <Suspense
fallback={<S.CharacterGrid>로딩 중</S.CharacterGrid>}> <CharacterGrid />
</Suspense> with it so that errors from CharacterGrid show a compact local error
UI (retry/button) instead of bubbling to the global RouteErrorBoundary—keep
existing QueryClient throwOnError behavior unchanged.
src/mocks/handlers/character.ts (1)

6-6: 핸들러 함수에 await 없이 async가 선언되어 있습니다.

핸들러 내부에 await 구문이 없으므로 async 키워드는 불필요합니다.

♻️ 수정 제안
-  http.get('/api/v1/character/collection', async ({ request }) => {
+  http.get('/api/v1/character/collection', ({ request }) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mocks/handlers/character.ts` at line 6, The handler passed to
http.get('/api/v1/character/collection') is declared async but contains no await
expressions; remove the unnecessary async keyword from that handler function
(the anonymous callback for http.get('/api/v1/character/collection')) so the
signature becomes a plain synchronous function to avoid misleading async usage.
src/features/character-management/ui/CharacterItem/index.tsx (1)

15-17: CharacterCardPropsCharacterItemProps가 동일한 구조입니다.

두 인터페이스 모두 character: CharacterItemData 하나만 가지므로 하나로 통합하거나 타입 별칭을 사용할 수 있습니다.

♻️ 수정 제안
-interface CharacterCardProps {
-  character: CharacterItemData;
-}
-
 function CharacterCard({ character }: CharacterCardProps) {
 interface CharacterItemProps {
   character: CharacterItemData;
 }
+
+type CharacterCardProps = CharacterItemProps;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/character-management/ui/CharacterItem/index.tsx` around lines 15
- 17, CharacterCardProps and CharacterItemProps both define the same shape
(character: CharacterItemData); remove the duplicate interface by keeping one
(e.g., CharacterItemProps) or create a single type alias (e.g., type
CharacterProps = { character: CharacterItemData }) and update all references to
use that single symbol; ensure you update all occurrences/imports/usages of
CharacterCardProps and CharacterItemProps across the component and any consumers
so they reference the consolidated type.
src/features/character-management/ui/CharacterItem/index.styles.ts (1)

15-22: CardContainer의 테두리 색이 배경색과 동일합니다.

border: 1px solid ${theme.color.semantic.orange.subtle}의 색상이 CardContainerBase에서 설정된 background-color와 동일합니다. 레이아웃 일관성을 위해 테두리 공간을 유지하려는 의도라면, transparent를 명시적으로 사용하는 것이 설계 의도를 더 명확하게 전달합니다.

♻️ 수정 제안
 export const CardContainer = styled(CardContainerBase)`
   justify-content: flex-end;
   padding-top: ${({ theme }) => theme.unit[32]};
   padding-bottom: ${({ theme }) => theme.unit[16]};
   padding-left: ${({ theme }) => theme.unit[16]};
   padding-right: ${({ theme }) => theme.unit[16]};
-  border: 1px solid ${({ theme }) => theme.color.semantic.orange.subtle};
+  border: 1px solid transparent;
 `;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/character-management/ui/CharacterItem/index.styles.ts` around
lines 15 - 22, The CardContainer currently sets a 1px solid border using
theme.color.semantic.orange.subtle which equals the CardContainerBase
background; replace that border color with transparent (explicitly) to preserve
the border space while making the intent clear — update the styled component
CardContainer (which extends CardContainerBase) to use border: 1px solid
transparent so the gap remains but no visible matching-color border appears.
src/features/character-management/ui/CharacterGrid/index.tsx (2)

5-5: 컴포넌트 내부 함수명(CharacterList)과 외부에 노출된 이름(CharacterGrid)이 불일치합니다.

React DevTools 및 에러 스택트레이스에는 CharacterList로 표시되지만, 배럴(ui/index.ts)에서 CharacterGrid로 re-export됩니다. 디버깅 시 혼동을 줄이기 위해 함수명을 CharacterGrid로 통일하는 것을 권장합니다.

♻️ 수정 제안
-function CharacterList() {
+function CharacterGrid() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/character-management/ui/CharacterGrid/index.tsx` at line 5, The
component function is declared as CharacterList but re-exported as
CharacterGrid, causing mismatched names in DevTools and stack traces; rename the
function declaration from CharacterList to CharacterGrid (and set
Component.displayName = 'CharacterGrid' if present) and update any internal
references/exports in this module so the declared name and the exported name
match (ensure the symbol CharacterGrid is used consistently).

10-11: keycharacter.name 대신 character.id를 사용하세요.

character.id는 DB의 안정적인 고유 식별자입니다. 이름은 서버 응답에서 변경될 수 있고 이론적으로 중복될 수 있어 React 재조정(reconciliation)에서 예기치 않은 동작을 유발할 수 있습니다.

♻️ 수정 제안
-        <CharacterItem key={character.name} character={character} />
+        <CharacterItem key={character.id} character={character} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/character-management/ui/CharacterGrid/index.tsx` around lines 10
- 11, Replace the unstable key prop on the CharacterItem elements: in the map
over characterCollection update the key prop from character.name to character.id
(i.e., <CharacterItem key={character.id} character={character} />), and ensure
character.id is present/non-null on the character objects before rendering (or
fall back to a stable unique value) so React reconciliation uses the DB-stable
identifier.
🤖 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/entities/character/api/character.ts`:
- Around line 4-9: The getCharacterCollection function (and other endpoint
wrappers like auth.ts, group.ts, expense.ts) currently hardcodes useMock: true
when calling axiosInstance, which can cause mock responses in production; update
these calls to only set useMock when running in development (e.g.,
process.env.NODE_ENV === 'development') or remove the flag and mark the endpoint
as TODO/unimplemented until real API integration is done, ensuring
axiosInstance.get calls no longer pass useMock: true unconditionally and that
any mock enabling is gated by the environment check; verify axiosInstance
interceptor (which reads useMock) will only see the flag in development.

In `@src/features/character-management/ui/CharacterGrid/index.tsx`:
- Line 6: The list rendering uses character.name as the React key which is
unstable; update the CharacterItem key to use the primary identifier
character.id instead. Locate where useGetCharacterCollection provides
characterCollection and where <CharacterItem key={character.name} ... /> is
rendered, and replace the key prop to use character.id (ensuring character has
an id property) so React list reconciliation uses the numeric primary key.

In `@src/features/character-management/ui/CharacterItem/index.tsx`:
- Around line 7-12: EmptyCharacterCard currently renders only an icon
(Certified) with no accessible label, so add an explicit accessible name: update
EmptyCharacterCard (or S.EmptyCardContainer) to include an aria-label describing
the locked card (e.g., "Locked character card") or include visually-hidden text
inside the component for screen readers; ensure the accessible element has the
correct role if it's interactive and reference the Certified component and
S.EmptyCardContainer when making the change.
- Around line 26-28: The displayed date in CharacterItem is computed with
format(new Date(unlockedAt), ...) which converts the ISO/UTC timestamp to the
local timezone and can shift the calendar date; change this to formatInTimeZone
from date-fns-tz (or explicitly parse the UTC date-part) so the rendered Text
for unlockedAt always shows the UTC calendar date regardless of client timezone;
update the import and replace format(new Date(unlockedAt), 'yyyy.MM.dd') with
formatInTimeZone(unlockedAt, 'UTC', 'yyyy.MM.dd') (or implement a UTC-only
parsing routine) to fix the off-by-one-day issue.

In `@src/features/character-management/ui/CharacterSection/index.tsx`:
- Line 14: Update the Suspense fallback to use the correct Korean spacing and
match other fallbacks: replace the current "로딩중" inside the Suspense fallback
S.CharacterGrid with "로딩 중..." so the text uses the proper spacing and includes
the ellipsis; look for the Suspense component and its fallback prop and update
the string accordingly.

---

Nitpick comments:
In `@src/entities/character/model/character.type.ts`:
- Around line 15-19: The CharacterItemData interface currently allows invalid
combinations of isUnlocked and unlockedAt; change it to a discriminated union so
the relationship is enforced: define CharacterItemData as either (isUnlocked:
true; unlockedAt: string; plus CharacterData) or (isUnlocked: false; unlockedAt:
null; plus CharacterData). Update any code that constructs or narrows
CharacterItemData to use the discriminant (isUnlocked) for type guards and
ensure places that previously assumed unlockedAt may be string now handle the
null case when isUnlocked is false.

In `@src/features/character-management/api/useGetCharacterCollection.ts`:
- Line 6: The current queryKey used in useGetCharacterCollection is too generic
(['characters']) and can collide with other character queries; update the
queryKey in the useGetCharacterCollection hook to a more specific key such as
['characters', 'collection'] (or ['characters', 'collection', params] if there
are filter/owner params) so TanStack Query cache entries are namespaced
per-purpose and per-parameters; locate the queryKey assignment inside the
useGetCharacterCollection implementation and replace the array accordingly,
ensuring any dependent invalidations or usages (e.g.,
queryClient.invalidateQueries(['characters'])) are updated to the new key shape.

In `@src/features/character-management/ui/CharacterGrid/index.tsx`:
- Line 5: The component function is declared as CharacterList but re-exported as
CharacterGrid, causing mismatched names in DevTools and stack traces; rename the
function declaration from CharacterList to CharacterGrid (and set
Component.displayName = 'CharacterGrid' if present) and update any internal
references/exports in this module so the declared name and the exported name
match (ensure the symbol CharacterGrid is used consistently).
- Around line 10-11: Replace the unstable key prop on the CharacterItem
elements: in the map over characterCollection update the key prop from
character.name to character.id (i.e., <CharacterItem key={character.id}
character={character} />), and ensure character.id is present/non-null on the
character objects before rendering (or fall back to a stable unique value) so
React reconciliation uses the DB-stable identifier.

In `@src/features/character-management/ui/CharacterItem/index.styles.ts`:
- Around line 15-22: The CardContainer currently sets a 1px solid border using
theme.color.semantic.orange.subtle which equals the CardContainerBase
background; replace that border color with transparent (explicitly) to preserve
the border space while making the intent clear — update the styled component
CardContainer (which extends CardContainerBase) to use border: 1px solid
transparent so the gap remains but no visible matching-color border appears.

In `@src/features/character-management/ui/CharacterItem/index.tsx`:
- Around line 15-17: CharacterCardProps and CharacterItemProps both define the
same shape (character: CharacterItemData); remove the duplicate interface by
keeping one (e.g., CharacterItemProps) or create a single type alias (e.g., type
CharacterProps = { character: CharacterItemData }) and update all references to
use that single symbol; ensure you update all occurrences/imports/usages of
CharacterCardProps and CharacterItemProps across the component and any consumers
so they reference the consolidated type.

In `@src/features/character-management/ui/CharacterSection/index.tsx`:
- Around line 14-16: Change the Suspense fallback text from "로딩중" to "로딩 중" for
consistent spacing and add a local ErrorBoundary around the CharacterGrid to
improve UX on component-level failures; create or reuse a small boundary
component (e.g., LocalErrorBoundary or CharacterSectionErrorBoundary) and wrap
<Suspense fallback={<S.CharacterGrid>로딩 중</S.CharacterGrid>}> <CharacterGrid />
</Suspense> with it so that errors from CharacterGrid show a compact local error
UI (retry/button) instead of bubbling to the global RouteErrorBoundary—keep
existing QueryClient throwOnError behavior unchanged.

In `@src/mocks/handlers/character.ts`:
- Line 6: The handler passed to http.get('/api/v1/character/collection') is
declared async but contains no await expressions; remove the unnecessary async
keyword from that handler function (the anonymous callback for
http.get('/api/v1/character/collection')) so the signature becomes a plain
synchronous function to avoid misleading async usage.

In `@src/shared/assets/svgs/icon/Certified.tsx`:
- Around line 1-27: The SVG visually represents a padlock but the component is
named SvgCertified; rename the component and its export from SvgCertified to a
clear lock name (e.g., SvgLock or SvgPadlock) and rename the file accordingly,
update the default export to match the new identifier, and then update all
import sites (search for SvgCertified) and any index/exports or storybook
entries that reference it so names remain consistent across the codebase.

@yoouyeon
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

🧹 Nitpick comments (3)
src/features/character-management/ui/CharacterItem/index.styles.ts (2)

10-10: gapmargin-bottom이 중복 적용되어 간격이 누적됩니다.

CardContainerBasegap: theme.unit[4](Line 10)과 CharacterImagemargin-bottom: theme.unit[16](Line 38)이 동시에 적용됩니다. 플렉스 레이아웃에서 두 속성은 누적되므로, 이미지와 다음 형제 요소 사이의 실제 간격은 unit[16] + unit[4]가 됩니다.

의도한 디자인이라면 문제없지만, 간격 관리를 한 가지 방법으로 통일하면 유지보수가 용이합니다.

Also applies to: 38-38

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/character-management/ui/CharacterItem/index.styles.ts` at line
10, CardContainerBase and CharacterImage are both adding vertical spacing (gap:
theme.unit[4] on CardContainerBase and margin-bottom: theme.unit[16] on
CharacterImage) causing doubled spacing; pick one spacing mechanism and remove
the other — e.g., remove or zero out CharacterImage's margin-bottom and rely on
CardContainerBase's gap (or remove CardContainerBase's gap and keep
CharacterImage margin), and update the styles in index.styles.ts (adjust
CharacterImage or CardContainerBase) so only one spacing source controls the
space between the image and the next sibling.

15-22: CardContainer의 테두리 색상이 배경색과 동일한 토큰을 사용하고 있습니다.

Line 21의 border 색상으로 theme.color.semantic.orange.subtle을 사용하고 있는데, 이는 CardContainerBasebackground-color(Line 12)와 동일한 토큰입니다. 결과적으로 테두리가 배경에 가려져 시각적으로 보이지 않게 됩니다.

LockedCharacterCard와의 크기 일관성 유지를 위해 의도적으로 투명 테두리를 설정한 것이라면 괜찮습니다. 하지만 별도의 시각적 테두리를 의도했다면 다른 색상 토큰 사용을 고려해 주세요.

🎨 테두리 색상 토큰 변경 예시
  border: 1px solid ${({ theme }) => theme.color.semantic.orange.subtle};
+ border: 1px solid ${({ theme }) => theme.color.semantic.orange.default};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/character-management/ui/CharacterItem/index.styles.ts` around
lines 15 - 22, CardContainer의 border에 background와 동일한
토큰(theme.color.semantic.orange.subtle)이 사용돼 테두리가 보이지 않으므로 CardContainer 스타일에서
border 색상 토큰을 배경과 다른 대비 있는 토큰으로 교체하세요 (예: theme.color.semantic.orange.default 또는
theme.color.border.default 등 프로젝트의 명시적 경계 색상 토큰). 변경 대상는
CardContainer(styled(CardContainerBase))의 border 속성이며, 만약 의도적으로 투명 테두리를 사용한 것이라면
주석으로 의도 명시 또는 기존 토큰 유지 후 LockedCharacterCard와의 크기/시각 일관성이 유지되는지 확인하도록 하세요.
src/features/character-management/ui/CharacterSection/index.tsx (1)

1-21: Suspense와 함께 ErrorBoundary도 추가하는 것을 권장합니다.

CharacterGrid가 데이터 페칭 중 네트워크 오류 등으로 Error를 throw할 경우, 현재 구조에서는 상위 라우트 레벨의 RouteErrorBoundary로 전파됩니다. React의 Suspense 기반 데이터 페칭 패턴에서는 컴포넌트 레벨에서 SuspenseErrorBoundary를 함께 사용하여 더 세밀한 에러 처리와 기능별 독립적인 폴백 UI를 제공하는 것이 표준입니다.

♻️ ErrorBoundary 추가 제안
-import { Suspense } from 'react';
+import { Suspense } from 'react';
+import { ErrorBoundary } from 'react-error-boundary';
 import Text from '@/shared/ui/Text';
 import CharacterGrid from '../CharacterGrid';
 import * as S from './index.styles';

 function CharacterSection() {
   return (
     <S.Container>
       <S.TitleWrapper>
         <Text color="semantic.text.strong" variant="title">
           캐릭터 도감
         </Text>
       </S.TitleWrapper>
+      <ErrorBoundary fallback={<S.CharacterGrid>불러오기 실패</S.CharacterGrid>}>
         <Suspense fallback={<S.CharacterGrid>로딩 중...</S.CharacterGrid>}>
           <CharacterGrid />
         </Suspense>
+      </ErrorBoundary>
     </S.Container>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/character-management/ui/CharacterSection/index.tsx` around lines
1 - 21, Wrap the Suspense that renders CharacterGrid in a component-level
ErrorBoundary to catch render/fetch errors locally instead of letting them
bubble to the route; in CharacterSection import or create an ErrorBoundary (or
use your existing ErrorBoundary/RouteErrorBoundary-compatible component) and
place it around <Suspense fallback={...}><CharacterGrid/></Suspense>, providing
a small error fallback UI (e.g., retry or message) so CharacterSection handles
CharacterGrid errors independently; update imports and ensure the component name
CharacterSection, Suspense and CharacterGrid are the ones referenced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/features/character-management/ui/CharacterItem/index.styles.ts`:
- Line 10: CardContainerBase and CharacterImage are both adding vertical spacing
(gap: theme.unit[4] on CardContainerBase and margin-bottom: theme.unit[16] on
CharacterImage) causing doubled spacing; pick one spacing mechanism and remove
the other — e.g., remove or zero out CharacterImage's margin-bottom and rely on
CardContainerBase's gap (or remove CardContainerBase's gap and keep
CharacterImage margin), and update the styles in index.styles.ts (adjust
CharacterImage or CardContainerBase) so only one spacing source controls the
space between the image and the next sibling.
- Around line 15-22: CardContainer의 border에 background와 동일한
토큰(theme.color.semantic.orange.subtle)이 사용돼 테두리가 보이지 않으므로 CardContainer 스타일에서
border 색상 토큰을 배경과 다른 대비 있는 토큰으로 교체하세요 (예: theme.color.semantic.orange.default 또는
theme.color.border.default 등 프로젝트의 명시적 경계 색상 토큰). 변경 대상는
CardContainer(styled(CardContainerBase))의 border 속성이며, 만약 의도적으로 투명 테두리를 사용한 것이라면
주석으로 의도 명시 또는 기존 토큰 유지 후 LockedCharacterCard와의 크기/시각 일관성이 유지되는지 확인하도록 하세요.

In `@src/features/character-management/ui/CharacterSection/index.tsx`:
- Around line 1-21: Wrap the Suspense that renders CharacterGrid in a
component-level ErrorBoundary to catch render/fetch errors locally instead of
letting them bubble to the route; in CharacterSection import or create an
ErrorBoundary (or use your existing ErrorBoundary/RouteErrorBoundary-compatible
component) and place it around <Suspense
fallback={...}><CharacterGrid/></Suspense>, providing a small error fallback UI
(e.g., retry or message) so CharacterSection handles CharacterGrid errors
independently; update imports and ensure the component name CharacterSection,
Suspense and CharacterGrid are the ones referenced.

@yoouyeon
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

코드 확인했습니다! 간단한 질문 및 코멘트 남겨두었습니다. 확인해주시면 감사하곘습니다!


export const CardContainerBase = styled.li`
box-sizing: border-box;
aspect-ratio: 171 / 196;
Copy link
Contributor

Choose a reason for hiding this comment

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

카드 비율이 해당 비율로 고정이라 aspect-ratio를 쓰신 걸까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 맞습니다! 피그마에 카드 크기가 width: 171px; height: 196px; 로 되어 있어서 비율 맞춰 두었습니다.
이미지 사이즈가 햄스터 종류마다 달라서 🥹 카드 사이즈 맞추기가 애매해서 aspect-ratio로 비율을 지정하는 방법을 썼어요 😩

<S.CharacterImage src={imageUrl} alt={name} />
<Text variant="body2Sb">{name}</Text>
<Text variant="caption">
{unlockedAt ? format(new Date(unlockedAt), 'yyyy.MM.dd') : null}
Copy link
Contributor

Choose a reason for hiding this comment

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

자주 쓰이는 포맷이라면 날짜 포맷팅 유틸로 따로 분리해도 좋을 것 같습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

저희 전체 코드에서 날짜 포맷팅을 사용하는 패턴이 아래 4가지더라고요!

format(new Date(unlockedAt), 'yyyy.MM.dd')
format(new Date(), 'yyyy-MM-dd')
format(new Date(expense.date), 'yyyy년 M월 d일')
format(date, 'yyyy년 M월')

각각 포맷이 달라서,,, 이 부분은 유틸로 분리하지 않고 그냥 두도록 하겠습니다 😂

Base automatically changed from feat/MD-5 to develop February 22, 2026 13:41
@yoouyeon yoouyeon merged commit eab84af into develop Feb 22, 2026
1 check passed
@yoouyeon yoouyeon deleted the feat/MD-6 branch February 22, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 신규 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants