Skip to content

[Feat] 예적금 검색페이지 구현#60

Merged
1jiwoo27 merged 29 commits intodevelopfrom
feature/search-page/#31
Dec 1, 2025
Merged

[Feat] 예적금 검색페이지 구현#60
1jiwoo27 merged 29 commits intodevelopfrom
feature/search-page/#31

Conversation

@jihun3666
Copy link
Contributor

@jihun3666 jihun3666 commented Nov 28, 2025

📌 Related Issue

✅ Checklist

  • PR 제목의 형식을 잘 작성했나요? e.g. [Feat] PR 템플릿 작성
  • 빌드가 성공했나요? (pnpm build)
  • 이슈는 등록했나요?
  • 리뷰어와 라벨을 지정했나요?

📚 Tasks

예적금 검색 페이지를 구현하고 홈페이지 버튼과 연결했습니다.

Summary by CodeRabbit

  • 새로운 기능

    • 예금/적금 검색 페이지 추가 — 은행 선택 그리드(전체선택 포함), 기간 선택, 검색, 결과 정렬 및 로딩·오류 처리
    • 라우팅에 예금/적금 검색 경로 추가
  • 개선사항

    • 리스트 조회 시 선택한 은행과 기간을 쿼리로 전달
    • 드롭다운 컴포넌트가 외부 제어(value/onChange)로 변경
    • 새로운 상태관리 훅으로 검색 상태 관리 추가
  • 스타일

    • 검색 페이지·관련 컴포넌트 스타일 추가 및 조정(로고 SVG 규칙 포함)
    • 드롭다운 폭, 리스트 커서, 비교 뷰 폭, 테마에 xxs 크기 추가

✏️ Tip: You can customize this high-level summary in your review settings.

@jihun3666 jihun3666 requested a review from 1jiwoo27 November 28, 2025 23:52
@jihun3666 jihun3666 self-assigned this Nov 28, 2025
@jihun3666 jihun3666 linked an issue Nov 28, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Warning

Rate limit exceeded

@1jiwoo27 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 6 minutes and 43 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9fa3ab2 and 18b6ab7.

📒 Files selected for processing (1)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'ignored_paths'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

예금·적금 검색 페이지(컴포넌트 + CSS) 추가, DropDown을 제어형으로 전환, API에 선택적 쿼리 파라미터(bankNames, term) 추가, 라우트·홈 네비게이션 등록 및 소규모 스타일·UI 조정 포함. (최대 50단어)

Changes

Cohort / File(s) 요약
예금 검색 페이지
src/pages/deposit-search/deposit-search-page.tsx, src/pages/deposit-search/deposit-search-page.css.ts
신규 DepositSearchPage 및 스타일 추가: 은행 그리드(개별/전체선택), 기간 제어형 DropDown 연동, 검색 버튼, 초기 전체선택(saveTerm=36) 후 조회, 로딩·오류 처리, 결과 정렬 및 DepositBasic 렌더링. CSS에 bankLogo svg 글로벌 규칙 포함.
적금 검색 페이지
src/pages/savings-search/savings-search-page.tsx, src/pages/savings-search/savings-search-page.css.ts
신규 SavingsSearchPage 및 스타일 추가: 예금 페이지와 유사한 은행 선택/전체선택 UI, 기간 선택, 검색 흐름, 초기 로드 및 결과 정렬, SavingBasic 렌더링. CSS에 bankLogo svg 글로벌 규칙 포함.
API 업데이트
src/shared/api/products.ts
getSavingsListgetDepositsList 시그니처 확장: 선택적 bankNames?: string[], term?: number 파라미터 수용. 제공 시 bankNames는 콤마 직렬화해 쿼리로 전송, term은 존재하면 쿼리로 포함.
상태 저장소 (Zustand)
src/shared/stores/useSavingsSearchStore.ts, src/shared/stores/useDepositSearchStore.ts
신규 Zustand 훅 추가: 검색 UI 상태 관리(selectedBanks, saveTerm, list, initialized) 및 뮤테이터(setSelectedBanks, setSaveTerm, setList, reset 등).
드롭다운 컴포넌트 변경
src/shared/components/dropdown/dropdown.tsx, src/shared/components/dropdown/dropdown.css.ts
DropDown을 제어형으로 전환: DropDownProps 추가, `value?: number
라우팅 및 경로 추가
src/shared/router/index.tsx, src/shared/router/path.ts
PATHSAVINGS_SEARCH(/savings/search) 및 DEPOSIT_SEARCH(/deposit/search) 추가하고 해당 페이지를 보호된 라우트로 등록.
홈 페이지 네비게이션
src/pages/home/home-page.tsx
홈 액션 버튼에 FontAwesome 아이콘 추가 및 클릭 시 적금/예금 검색 페이지로 네비게이션 후 부드러운 스크롤 수행(로딩/비로빙 경로 모두).
은행 리스트 상수 및 에셋 매핑
src/shared/constants/bank-list.ts
BankItem 타입 및 BANK_LIST 상수 추가(은행 id, name, logo 컴포넌트 매핑).
UI 소소 변경
src/shared/components/deposit-basic/deposit-basic.css.ts, src/shared/components/savings-basic/savings-basic.css.ts
depositContainersavingsContainercursor: 'pointer' 추가.
비교 컴포넌트 스타일 조정
src/shared/components/deposit-compare/deposit-compare.css.ts, src/shared/components/savings-compare/savings-compare.css.ts
detailContainer 너비를 10rem13rem으로 확장.
텍스트/포맷팅 미세조정
src/shared/components/deposit-popular/deposit-popular.tsx, src/shared/components/savings-popular/savings-popular.tsx
이모지와 텍스트 사이 공백 추가(표현상 변경).
테마 토큰
src/styles/theme.css.ts
사이즈 토큰 xxs: '1.4rem' 추가.
패키지
package.json
zustand 의존성 추가(^5.0.9).

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant UI as SearchPage (React)
    participant Store as Zustand Store
    participant ClientAPI as 클라이언트(get*List)
    participant Backend as 서버 API

    User->>UI: 페이지 진입
    UI->>Store: setSelectedBanks(all), setSaveTerm(36), setInitialized(true)
    UI->>UI: loading=true
    UI->>ClientAPI: get*List(bankNames=all, term=36)
    ClientAPI->>Backend: GET /{deposits|savings}?bankNames=...&term=36
    Backend-->>ClientAPI: ProductList[] 반환
    ClientAPI-->>UI: ProductList[] 전달
    UI->>UI: 정렬(maxRate ↓, baseRate ↓), setList, loading=false

    User->>UI: 은행 토글 / DropDown 변경
    UI->>Store: setSelectedBanks / setSaveTerm
    User->>UI: 검색 버튼 클릭
    UI->>ClientAPI: get*List(selectedBanksOrUndefined, term)
    ClientAPI->>Backend: GET /{deposits|savings}?bankNames=...&term=...
    Backend-->>ClientAPI: 필터링된 결과 반환
    ClientAPI-->>UI: 결과 전달
    UI->>UI: 정렬 및 렌더링
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

  • 추가 검토 권장 항목:
    • src/shared/components/dropdown/dropdown.tsx: 제어형 전환으로 기존 사용처 호환성(널/언디파인드 처리).
    • src/shared/api/products.ts: bankNames 직렬화(인코딩), term이 없는 경우 쿼리 미포함 동작 확인.
    • src/pages/deposit-search/*, src/pages/savings-search/*: 초기 전체선택/초기화(initialized) 로직과 선택이 없을 때 전체 조회 의도 검증, 정렬 로직 정확성.
    • src/shared/constants/bank-list.ts: 로고 컴포넌트 임포트 경로 및 SVG 렌더링 호환성.
    • 글로벌 스타일 변경(bankLogo svg) 및 레이아웃 변경(dropdownWrapper 너비, compare detail width)이 UI에 미치는 영향 확인.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 일부 변경사항이 주요 요구사항 범위를 벗어나 있습니다: 홈페이지 버튼 아이콘 추가, DropDown 너비 조정, 상세비교 페이지 너비 조정 등. 홈페이지 아이콘 추가 및 UI 스타일 조정 변경사항들을 별도 이슈로 분리하거나 현재 PR 범위에서 제거하기를 권장합니다.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 예적금 검색페이지 구현이라는 주요 변경사항을 명확하고 간결하게 요약하고 있습니다.
Linked Issues check ✅ Passed PR은 예금/적금 검색 페이지 구현 요구사항을 충족하며, 필터링 조건(은행선택, 기간선택)을 포함한 완전한 구현을 제공합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

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

🧹 Nitpick comments (14)
src/shared/api/products.ts (1)

4-10: 선택 파라미터 처리 로직은 적절하며, term 처리만 약간 더 명시적으로 다듬을 수 있습니다

bankNames를 빈 배열일 때 제외하고 있을 때만 join(',')으로 직렬화하는 방식은 백엔드 필터링 요구사항에 잘 맞는 구현 같습니다.
term: term || undefined는 현재 옵션 값이 1/3/6/12/24/36인 상황에서는 문제 없지만, 나중에 0 같은 값이 들어올 가능성을 고려하면 다음처럼 null 병합 연산자를 쓰면 의도가 더 분명합니다.

-      term: term || undefined 
+      term: term ?? undefined

필수는 아니고, 추후 확장성을 위한 선택적 정리로 보입니다.

Also applies to: 19-25

src/shared/components/dropdown/dropdown.tsx (1)

11-15: 제어형 전환은 잘 되어 있고, value 표시 조건만 조금 정리하면 좋겠습니다

  • value를 부모에서 제어하도록 바꾼 구조는 DropDown 재사용성과 예적금 검색 페이지 요구사항에 잘 맞습니다.
  • 다만 placeholder 부분은 약간 정리할 여지가 있습니다.
<div className={styles.dropdownPlaceholder}>
-  {value ? `${value}개월` : DEFAULT_PLACEHOLDER || DEFAULT_PLACEHOLDER}
+  {value != null ? `${value}개월` : DEFAULT_PLACEHOLDER}
</div>

이렇게 하면:

  • 0 같은 값이 들어와도 정상적으로 표시되고(미래 확장 대비),
  • 불필요한 || DEFAULT_PLACEHOLDER 중복도 제거됩니다.

기능 변경은 없고 가독성/명시성 개선 수준입니다.

Also applies to: 17-25, 35-35, 46-52

src/shared/components/deposit-basic/deposit-basic.css.ts (1)

4-13: cursor: 'pointer' 추가는 상호작용 카드라는 의도를 잘 드러냅니다

예금 카드(depositContainer)를 클릭해서 상세 페이지로 이동하는 패턴이라면 pointer 커서 추가가 자연스럽고 UX에도 도움이 됩니다.
다만 실제로 카드 전체에 onClick이나 링크가 연결되어 있는지 한 번만 확인해 주시면 좋겠습니다. (시각적으로 클릭 가능해 보이는데 아무 동작이 없으면 혼란을 줄 수 있습니다.)

src/shared/router/path.ts (1)

1-10: PATH 상수 추가는 적절하지만, 사용처에서 하드코딩된 문자열도 함께 치환하면 좋겠습니다

SAVINGS_SEARCH: '/savings/search', DEPOSIT_SEARCH: '/deposit/search' 상수 추가는 라우터 설정과 잘 맞습니다.
다만 HomePage에서 navigate('/deposit/search'), navigate('/savings/search')처럼 문자열을 직접 쓰고 있으니, 거기도 PATH.DEPOSIT_SEARCH, PATH.SAVINGS_SEARCH를 사용하도록 통일해 두면 변경에 더 강해질 것 같습니다.

src/pages/home/home-page.tsx (1)

52-77: 검색 페이지 이동 버튼은 좋지만, 경로 상수 사용과 중복 제거를 고려해 보세요

  • 홈에서 바로 예금/적금 검색 페이지로 이동할 수 있게 만든 UX 자체는 매우 좋습니다.
  • 다만:
    • navigate('/deposit/search'), navigate('/savings/search')PATH.DEPOSIT_SEARCH, PATH.SAVINGS_SEARCH를 사용하면 라우터와 한 곳에서 관리할 수 있습니다.
    • 로딩 분기(if (Loading))와 일반 렌더 둘 다에서 거의 동일한 버튼 JSX가 반복되고 있어, 작은 컴포넌트로 추출하거나 공통 렌더 함수로 빼면 유지보수가 더 수월할 것 같습니다.

예시 방향(개념):

// 공통 버튼 렌더 함수
const renderMainButtons = () => (
  <div className={styles.mainButtonWrapper}>
    {/* 예금 버튼 */}
    {/* 적금 버튼 */}
  </div>
);

// 로딩/비로딩 모두에서 renderMainButtons() 호출

필수는 아니지만, 향후 스타일이나 텍스트를 수정할 때 실수 가능성을 줄여 줄 것 같습니다.

Also applies to: 88-108

src/pages/savings-search/savings-search-page.tsx (3)

48-59: 데이터 정렬 로직은 괜찮지만, 에러 처리와 타입을 조금 더 명시적으로 하면 좋겠습니다

const fetchSavingList = async (banks: string[] | null, term: number) => {
  ...
  const res = await getSavingsList(banks || undefined, term);
  let data = res?.result || [];
  data = data.sort((a:ProductList, b:ProductList) => b.maxRate-a.maxRate);
  setSavingList(data);
}
  • maxRate 기준 내림차순 정렬은 의도에 잘 맞습니다.
  • 다만:
    • let data = res?.result || [];any로 흘러갈 수 있어 ProductList[]로 타입을 고정해 두면 좋습니다.
    • catch (error) {}에서 에러를 완전히 무시하고 있어, 최소한 console.error 로그를 남기거나, 에러 상태를 별도 state로 관리해 사용자에게 "조회 실패" 메시지를 보여주는 게 좋겠습니다.

예시:

let data: ProductList[] = res?.result ?? [];
data = [...data].sort((a, b) => b.maxRate - a.maxRate);
// 혹은 res 오류 시 savingList를 빈 배열로 초기화

검색 페이지 특성상 네트워크 오류와 "검색 결과 없음"을 구분해 주면 UX가 더 좋아질 것 같습니다.


79-84: 은행이 하나도 선택되지 않았을 때의 처리 의도는 맞지만, 코드 의도를 조금 더 드러내면 좋겠습니다

현재 로직:

const handleSearch = () => {
  if (selectedBanks.length === 0) {
    setSelectedBanks(BANK_LIST.map(b=>b.id));
  }
  fetchSavingList(selectedBanks.length > 0 ? selectedBanks : null, saveTerm);
};
  • 선택된 은행이 없으면 UI 상으로는 전체 은행을 다시 선택하고, API 호출은 null을 넘겨 "전체 조회"로 처리하는 구조입니다.
  • 동작 자체는 문제 없지만, setSelectedBanks는 비동기라서 처음 읽을 때는 "state가 반영되지 않은 값으로 호출되는 것 아닌가?" 하는 오해를 부를 수 있습니다.

의도를 더 분명히 하는 방향 예시:

const handleSearch = () => {
  const banks = selectedBanks.length > 0 ? selectedBanks : null;
  if (!banks) {
    setSelectedBanks(BANK_LIST.map(b => b.id));
  }
  fetchSavingList(banks, saveTerm);
};

기능 변화 없이 가독성과 의도 전달을 개선하는 수준이라 여유 있을 때 고려해 보시면 좋겠습니다.


92-139: 로딩/일반 렌더링에서 은행/기간/검색 버튼 UI가 중복되어 있습니다

if (loading) 분기와 그 아래 기본 return 블록을 보면, Header + 은행 선택 섹션 + 기간 선택 섹션 + 검색 버튼까지 대부분이 동일하고, 차이는 결과 리스트 유무 정도입니다.

이렇게 중복이 많으면:

  • 스타일이나 텍스트를 수정할 때 한쪽만 바뀌는 버그가 생기기 쉽고,
  • 유지보수 난이도가 올라갑니다.

구조를 다음과 같이 정리해 볼 수 있습니다.

  • 공통 부분(헤더, 은행/기간 선택, 검색 버튼)을 하나의 JSX 블록/컴포넌트로 추출
  • savingList 아래 영역에서만 loading 여부에 따라 스피너/스켈레톤 vs 결과 리스트를 분기

개략 예:

const renderForm = () => (
  <>
    {/* 은행 선택 섹션 */}
    {/* 기간 선택 섹션 */}
    {/* 검색 버튼 */}
  </>
);

return (
  <>
    <Header />
    <main className={styles.MainContainer}>
      {renderForm()}
      <div className={styles.savingListContainer}>
        {loading ? <LoadingSkeleton /> : <ResultList />}
      </div>
    </main>
  </>
);

지금 구조에서도 동작은 문제 없지만, PR 단위에서 함께 정리해 두면 이후 검색 조건이 더 늘어날 때도 훨씬 편할 것 같습니다.

Also applies to: 142-205

src/pages/deposit-search/deposit-search-page.tsx (4)

47-58: 데이터 타입 명시와 에러 처리 보완을 고려해 주세요.

현재 res?.result가 사실상 any로 취급되고, catch 블록이 비어 있어 API 오류 시 조용히 실패하고 단순히 빈 결과처럼 보일 수 있습니다. 타입 안정성과 디버깅 편의를 위해 결과를 ProductList[]로 단언/가드하고, 최소한 콘솔 로그나 에러 상태 관리를 추가하는 편이 좋겠습니다.

-    const fetchDepositList = async (banks: string[] | null, term: number) => {
-        setIsLoading(true);
-        try {
-            const res = await getDepositsList(banks || undefined, term);
-            let data = res?.result || [];
-            data = data.sort((a:ProductList, b:ProductList) => b.maxRate-a.maxRate);
-            setDepositList(data);
-        } catch (error) {
-        } finally {
-            setIsLoading(false);
-        }
-    };
+    const fetchDepositList = async (banks: string[] | null, term: number) => {
+        setIsLoading(true);
+        try {
+            const res = await getDepositsList(banks || undefined, term);
+            const data: ProductList[] = (res?.result ?? []).sort(
+                (a, b) => b.maxRate - a.maxRate,
+            );
+            setDepositList(data);
+        } catch (error) {
+            // TODO: 사용자에게 에러를 보여줄 계획이 없다면 최소한 로그는 남겨 두는 편이 좋습니다.
+            console.error('예금 상품 조회 실패:', error);
+        } finally {
+            setIsLoading(false);
+        }
+    };

78-83: 검색 로직을 한 번에 읽기 쉽게 정리하면 좋겠습니다.

현재는 selectedBanks.length === 0일 때 setSelectedBanksfetchDepositList(null, …)를 함께 호출하는 형태라 의도가 바로 눈에 들어오지 않습니다. “선택이 없으면 전체 선택 후 그 목록으로 검색한다”는 의도가 드러나도록, 유효한 은행 목록을 먼저 계산해서 상태와 API 호출에 함께 사용하는 식으로 단순화하면 가독성이 좋아집니다.

-    const handleSearch = () => {
-        if (selectedBanks.length === 0) {
-            setSelectedBanks(BANK_LIST.map(b=>b.id));
-        }
-        fetchDepositList(selectedBanks.length > 0 ? selectedBanks : null, saveTerm);
-    };
+    const handleSearch = () => {
+        const effectiveBanks =
+            selectedBanks.length > 0 ? selectedBanks : BANK_LIST.map((b) => b.id);
+
+        if (selectedBanks.length === 0) {
+            setSelectedBanks(effectiveBanks);
+        }
+
+        fetchDepositList(effectiveBanks, saveTerm);
+    };

91-139: 로딩 상태 분기에서 JSX가 거의 중복되고 있어 유지보수 비용이 높습니다.

if (loading) 분기와 최종 return 쪽이 리스트 영역만 제외하고 거의 동일해서, 추후 UI 변경 시 두 군데를 동시에 수정해야 합니다. 공통 레이아웃은 한 번만 렌더링하고, 하단 리스트 영역만 loading/데이터 없음/데이터 있음에 따라 분기하는 구조로 정리하면 코드가 더 단순해지고 실수 가능성도 줄어듭니다.

Also applies to: 141-205


186-201: 리스트 key를 더 유니크하게 설정하는 것이 안전해 보입니다.

현재 key가 productId만 사용되고 있는데, 같은 상품에 여러 optionId가 존재할 수 있다면 key 충돌 가능성이 있습니다. 이런 경우 optionId를 사용하거나 \${productId}-${optionId}``처럼 조합해서 key를 주는 편이 안전합니다.

src/pages/savings-search/savings-search-page.css.ts (1)

4-121: 전반적인 스타일 구조는 문제 없어 보이고, 예금 검색 페이지와의 일관성이 잘 맞습니다.

레이아웃/그리드/버튼 구조가 예금 검색 CSS와 거의 동일해 두 페이지 간 UX 일관성이 좋습니다. 추후 유지보수를 생각하면, 공통되는 스타일(컨테이너, 은행 그리드 등)을 별도 공통 모듈로 분리해 두면 색상만 바꿔 재사용하기 더 편할 것 같습니다.

src/pages/deposit-search/deposit-search-page.css.ts (1)

4-121: 예금 검색 페이지 스타일도 구조적으로 잘 잡혀 있고, 적금 검색 페이지와 색상만 다른 형태로 일관성이 있습니다.

공통 레이아웃과 컴포넌트(은행 버튼, 기간 섹션, 검색 버튼 등)가 이미 거의 동일하므로, 나중에 두 페이지가 더 늘어나면 이 파일과 적금 CSS에서 중복되는 부분을 공통 스타일로 추출해 두는 것을 고려해 볼 만합니다. 현재 상태로는 기능상 문제는 없어 보입니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1b1410 and 4eff1a2.

📒 Files selected for processing (11)
  • src/pages/deposit-search/deposit-search-page.css.ts (1 hunks)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/home/home-page.tsx (3 hunks)
  • src/pages/savings-search/savings-search-page.css.ts (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
  • src/shared/api/products.ts (2 hunks)
  • src/shared/components/deposit-basic/deposit-basic.css.ts (1 hunks)
  • src/shared/components/dropdown/dropdown.css.ts (1 hunks)
  • src/shared/components/dropdown/dropdown.tsx (3 hunks)
  • src/shared/router/index.tsx (1 hunks)
  • src/shared/router/path.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
src/shared/api/products.ts (2)
src/shared/api/axios.ts (1)
  • api (3-9)
src/shared/api/endpoints.ts (1)
  • PRODUCTS (8-15)
src/pages/deposit-search/deposit-search-page.tsx (5)
src/shared/api/products.ts (1)
  • getDepositsList (19-27)
src/pages/home/home-page.css.ts (1)
  • main (4-10)
src/pages/deposit-search/deposit-search-page.css.ts (1)
  • section (15-20)
src/shared/components/deposit-basic/deposit-basic.css.ts (1)
  • bank (15-19)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
src/shared/router/index.tsx (1)
src/shared/router/path.ts (1)
  • PATH (1-10)
src/pages/home/home-page.tsx (1)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
src/pages/deposit-search/deposit-search-page.css.ts (1)
src/styles/theme.css.ts (1)
  • vars (3-47)
src/pages/savings-search/savings-search-page.tsx (3)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
src/pages/savings-search/savings-search-page.css.ts (1)
src/styles/theme.css.ts (1)
  • vars (3-47)
🪛 GitHub Actions: Build Test
src/pages/deposit-search/deposit-search-page.tsx

[error] 95-95: Property 'mainContainer' does not exist on type 'typeof import("/home/runner/work/fini-client/fini-client/src/pages/deposit-search/deposit-search-page.css")'. Did you mean 'MainContainer'?


[error] 95-95: TypeScript error: Incorrect CSS module class reference. Ensure the CSS module exports the class 'mainContainer' or use the correct exported name (e.g., 'MainContainer').

🪛 GitHub Check: build
src/pages/deposit-search/deposit-search-page.tsx

[failure] 144-144:
Property 'mainContainer' does not exist on type 'typeof import("/home/runner/work/fini-client/fini-client/src/pages/deposit-search/deposit-search-page.css")'. Did you mean 'MainContainer'?


[failure] 95-95:
Property 'mainContainer' does not exist on type 'typeof import("/home/runner/work/fini-client/fini-client/src/pages/deposit-search/deposit-search-page.css")'. Did you mean 'MainContainer'?

src/pages/savings-search/savings-search-page.tsx

[failure] 145-145:
Property 'mainContainer' does not exist on type 'typeof import("/home/runner/work/fini-client/fini-client/src/pages/savings-search/savings-search-page.css")'. Did you mean 'MainContainer'?


[failure] 96-96:
Property 'mainContainer' does not exist on type 'typeof import("/home/runner/work/fini-client/fini-client/src/pages/savings-search/savings-search-page.css")'. Did you mean 'MainContainer'?

🔇 Additional comments (2)
src/shared/router/index.tsx (1)

10-11: 검색 페이지 라우팅 추가는 구조적으로 문제 없어 보입니다

  • PATH.SAVINGS_SEARCH, PATH.DEPOSIT_SEARCH를 ProtectedRoute로 감싸 기존 상세 페이지와 동일한 인증 정책을 적용한 점이 일관적입니다.
  • 페이지 컴포넌트 import 및 PATH 정의와도 잘 맞습니다.

별다른 이슈 없이 바로 사용할 수 있는 구조로 보입니다.

Also applies to: 20-21

src/shared/components/dropdown/dropdown.css.ts (1)

5-8: width를 100%로 풀어준 변경은 검색 폼 레이아웃에 잘 맞습니다

dropdownWrapperwidth: '100%'로 바꿔 부모 컨테이너 폭에 맞추도록 한 점은, 예적금 검색 페이지처럼 폼 요소를 한 줄에 정렬하는 레이아웃에 잘 어울립니다.
다른 페이지들에서 이 컴포넌트를 사용할 때도 보통 부모가 적절한 폭을 갖고 있을 것이므로, 부작용 가능성도 낮아 보입니다.

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

🧹 Nitpick comments (3)
src/pages/deposit-search/deposit-search-page.css.ts (2)

4-13: 고정 width(70rem) 대신 반응형 폭을 고려해보면 좋겠습니다.

mainContainer, bankContainer, termContainer 모두 width: '70rem'으로 고정되어 있어서 작은 화면에서는 가로 스크롤이 생길 수 있습니다. maxWidth + width: '100%' 패턴으로 바꾸면 데스크톱 레이아웃은 유지하면서도 더 유연해집니다.

예시 diff:

 export const mainContainer = style({
-    display: 'block',
-    width: '70rem',
+    display: 'block',
+    width: '100%',
+    maxWidth: '70rem',
@@
 export const bankContainer = style({
-    display: 'flex',
-    flexDirection: 'column',
-    width: '70rem',
+    display: 'flex',
+    flexDirection: 'column',
+    width: '100%',
@@
 export const termContainer = style({
-    display: 'flex',
-    gap: '1rem',
-    width: '70rem',
+    display: 'flex',
+    gap: '1rem',
+    width: '100%',

Also applies to: 28-34, 89-95


44-55: 버튼 계열 스타일의 hover/focus 상태 및 border 일관성 제안

bankButton, selectAllButton, searchButton 모두 클릭 가능한 요소지만, 커서 외에 시각적 피드백(hover/focus)이 없어 조금 밋밋할 수 있고, selectAllButton은 border 설정이 없는 반면 searchButtonborder: 'none'으로 명시돼 있어 약간 불일치가 있습니다. 접근성과 사용감 향상을 위해 공통 버튼 스타일에 hover/focus/disabled 등을 추가하는 것을 고려해보면 좋겠습니다.

예시(참고용):

 export const bankButton = style({
@@
-    borderRadius: '5px',
-    cursor: 'pointer',
+    borderRadius: '5px',
+    cursor: 'pointer',
+    selectors: {
+        '&:hover': {
+            borderColor: vars.color.pink300,
+        },
+        '&:focus-visible': {
+            outline: `2px solid ${vars.color.pink300}`,
+            outlineOffset: '2px',
+        },
+    },
 });

 export const selectAllButton = style({
@@
-    padding: '0.5rem 1rem',
+    padding: '0.5rem 1rem',
+    border: 'none',
 });

 export const searchButton = style({
@@
-    border: 'none',
+    border: 'none',
@@
-    width: '11rem',
+    width: '11rem',
+    selectors: {
+        '&:hover': {
+            backgroundColor: vars.color.pink400,
+        },
+        '&:focus-visible': {
+            outline: `2px solid ${vars.color.pink400}`,
+            outlineOffset: '2px',
+        },
+    },
 });

Also applies to: 66-69, 79-87, 97-114

src/pages/savings-search/savings-search-page.tsx (1)

92-140: 로딩/비로딩 분기에서 거의 동일한 JSX가 복제되어 유지보수 비용이 높습니다

if (loading) { return ( ... ) } 분기와 최종 return의 JSX가 Header, <main>, 은행/기간/검색 섹션까지 거의 동일하게 두 번 작성되어 있습니다.
이 상태에서는 레이아웃이나 스타일을 수정할 때 두 곳을 항상 같이 수정해야 해서, 빠뜨리기 쉽고 버그로 이어질 가능성이 큽니다.

한 개의 JSX 트리를 공통으로 반환하고,

  • 상단 필터 UI는 항상 동일하게 렌더링하고,
  • 상품 리스트 영역에서만 loading ? 로딩 UI : 결과/빈 상태 UI 로 분기하는 형태로 리팩터링해 보시는 것을 권장드립니다.

예: savingListContainer 안에서 loading이면 “로딩 중” 플레이스홀더나 스켈레톤을, 아니면 현재 리스트/빈 결과 문구를 보여 주는 방식으로 정리하면 중복이 크게 줄어듭니다.

Also applies to: 142-207

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4eff1a2 and 886dfbf.

📒 Files selected for processing (3)
  • src/pages/deposit-search/deposit-search-page.css.ts (1 hunks)
  • src/pages/savings-search/savings-search-page.css.ts (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/savings-search/savings-search-page.css.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/savings-search/savings-search-page.tsx (5)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/pages/home/home-page.css.ts (1)
  • main (4-10)
src/pages/deposit-search/deposit-search-page.css.ts (1)
  • section (15-20)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
🔇 Additional comments (4)
src/pages/deposit-search/deposit-search-page.css.ts (2)

36-43: 섹션/그리드 및 리스트 컨테이너 레이아웃 구성은 명확하고 일관적입니다.

bankGrid의 6열 그리드와 depositListContainer의 컬럼 배치는 구조가 단순하면서도 페이지 역할에 잘 맞고, gap/margin 사용도 깔끔합니다. 현재 요구사항 기준으로는 그대로 유지해도 충분해 보입니다.

Also applies to: 116-121


57-64: SVG 로고 컨테이너 + globalStyle 패턴이 적절합니다.

bankLogo에 고정 크기를 주고, globalStyle(\${bankLogo} svg`, …)`로 내부 SVG를 100% 채우도록 한 패턴이 일관된 로고 사이즈를 보장해 줘서 재사용성과 유지보수 측면에서 좋아 보입니다.

Also applies to: 123-126

src/pages/savings-search/savings-search-page.tsx (2)

10-40: 상품 타입 및 은행 메타데이터 정의가 명확합니다

ProductList 타입이 API 응답 구조를 잘 캡처하고 있고, BANK_LIST에 id/name/logo를 분리해 둔 구조도 이후 필터링/표시에 재사용하기 좋습니다. 현재 형태 그대로 유지해도 무방해 보입니다.


187-205: 결과 리스트 및 빈 상태 처리 로직이 직관적입니다

savingList.length > 0일 때는 SavingBasic으로 매핑하고, 그렇지 않으면 “검색된 적금 상품이 없습니다.”라는 문구를 보여주는 흐름이 명확하고 사용자 입장에서도 이해하기 좋습니다. 빈 결과 문구에서 “적금”으로 용어를 맞춘 부분도 페이지 맥락과 잘 어울립니다.

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: 0

♻️ Duplicate comments (1)
src/pages/deposit-search/deposit-search-page.tsx (1)

97-97: CSS 모듈 클래스 이름 불일치로 인한 빌드 실패 가능성이 여전히 있습니다.

이전 리뷰/빌드 로그에 따르면 CSS 파일에서는 MainContainer를 export하는데, 여기서는 styles.mainContainer를 사용하고 있어 타입/빌드 에러가 났었습니다. 현재 코드도 여전히 styles.mainContainer를 사용하고 있어서, CSS 쪽에서 이름을 변경하지 않았다면 동일 에러가 재발할 가능성이 큽니다.

-        <main className={styles.mainContainer}>
+        <main className={styles.MainContainer}>
...
-        <main className={styles.mainContainer}>
+        <main className={styles.MainContainer}>

CSS 모듈에서 실제로 export하는 이름(MainContainer vs mainContainer)을 다시 한 번 확인해서 둘 중 하나로 정리해 주세요.

Also applies to: 146-146

🧹 Nitpick comments (5)
src/pages/deposit-search/deposit-search-page.tsx (5)

47-59: 정렬 시 원본 배열 변이 및 에러 처리 개선 여지가 있습니다.

res?.result가 재사용되는 참조일 수 있어서, 바로 sort를 호출하면 원본을 변이할 수 있습니다. 그리고 에러 시에는 단순히 빈 배열만 세팅되어 원인 파악이 어렵습니다.

-        try {
-            const res = await getDepositsList(banks || undefined, term);
-            let data = res?.result || [];
-            data = data.sort((a:ProductList, b:ProductList) => b.maxRate-a.maxRate);
-            setDepositList(data);
+        try {
+            const res = await getDepositsList(banks || undefined, term);
+            const data: ProductList[] = [...(res?.result ?? [])].sort(
+                (a, b) => b.maxRate - a.maxRate,
+            );
+            setDepositList(data);
         } catch (error) {
-            setDepositList([]);
+            // TODO: 에러 로깅/알림 필요 시 여기에서 처리
+            setDepositList([]);
         } finally {

이렇게 하면 원본 변이를 피하면서 정렬하고, 이후 에러 로깅도 쉽게 확장할 수 있습니다.


61-65: 기본 기간(36개월) 매직 넘버를 상수/초기값으로 한 번만 관리하는 것이 좋습니다.

현재 useState<number>(36)fetchDepositList(null, 36)에 동일한 숫자가 중복되어 있어서 나중에 기본 기간을 바꾸면 한 군데를 놓치기 쉽습니다.

-const DepositSearchPage = () => {
-    const [depositList, setDepositList] = useState<ProductList[]>([]);
-    const [loading, setIsLoading] = useState(true);
-    const [selectedBanks, setSelectedBanks] = useState<string[]>([]);
-    const [saveTerm, setSaveTerm] = useState<number>(36);  
+const DEFAULT_TERM = 36;
+
+const DepositSearchPage = () => {
+    const [depositList, setDepositList] = useState<ProductList[]>([]);
+    const [loading, setIsLoading] = useState(true);
+    const [selectedBanks, setSelectedBanks] = useState<string[]>([]);
+    const [saveTerm, setSaveTerm] = useState<number>(DEFAULT_TERM);
...
-        setSelectedBanks(BANK_LIST.map(b=>b.id));
-        fetchDepositList(null, 36);
+        setSelectedBanks(BANK_LIST.map(b => b.id));
+        fetchDepositList(null, DEFAULT_TERM);

이렇게 하면 기본값 변경 시 한 곳만 수정하면 되어 유지보수가 쉬워집니다.


79-92: 검색 시 선택 은행 상태와 실제 조회 대상 은행을 한 번에 계산하는 편이 더 명확합니다.

현재 selectedBanks.length === 0일 때 setSelectedBanks를 호출한 뒤, 조건식에서 다시 BANK_LIST.map을 호출하고 있어 약간 중복/혼동 여지가 있습니다. 조회 대상 은행 배열을 한 번 계산해서 상태/조회에 같이 쓰면 의도가 더 분명합니다.

-    const handleSearch = () => {
-        if (loading) return;
-        if (selectedBanks.length === 0) {
-            setSelectedBanks(BANK_LIST.map(b=>b.id));
-        }
-        fetchDepositList(selectedBanks.length > 0 ? selectedBanks : BANK_LIST.map(b=>b.id), saveTerm);
-    };
+    const handleSearch = () => {
+        if (loading) return;
+
+        const targetBanks =
+            selectedBanks.length > 0 ? selectedBanks : BANK_LIST.map((b) => b.id);
+
+        if (selectedBanks.length === 0) {
+            setSelectedBanks(targetBanks);
+        }
+
+        fetchDepositList(targetBanks, saveTerm);
+    };

동일한 동작을 유지하면서도 “실제 어떤 은행들로 조회하는지”를 한눈에 볼 수 있습니다.


93-141: 로딩 상태와 일반 상태에서 동일 JSX가 중복되어 있어 유지보수 비용이 커질 수 있습니다.

if (loading) 분기와 일반 return에 필터/기간/검색 버튼 UI가 거의 동일하게 두 번 등장합니다. 이후 UI 변경 시 한쪽만 수정해서 동기화가 깨질 위험이 있습니다.

공통 레이아웃을 별도 컴포넌트나 렌더 함수로 분리하고, 로딩 여부에 따라 “목록 영역만” 달리 렌더하는 형태를 고려해 볼 만합니다.

예시 스케치:

const FilterSection = () => (
  <main className={styles.MainContainer /* 또는 mainContainer */}>
    {/* 은행/기간/검색 버튼 섹션 공통 */}
  </main>
);

if (loading) {
  return (
    <>
      <Header />
      <FilterSection />
      {/* 여기에는 스피너 / 스켈레톤 등만 추가 */}
    </>
  );
}

return (
  <>
    <Header />
    <FilterSection />
    {/* 여기에서만 depositList 렌더링 */}
  </>
);

이렇게 하면 UI 수정 시 한 곳만 손보면 되고, 테스트 관점에서도 중복이 줄어듭니다.

Also applies to: 143-206


189-201: 리스트 key에 productId만 사용하는 경우 중복 가능성을 한 번 점검해보는 것이 좋습니다.

DepositBasic의 key를 productId 하나만 쓰고 있는데, 동일 상품(productId) 내에 여러 옵션(optionId)이 있을 수 있다면 key 충돌이 날 수 있습니다. 그런 구조라면 두 값을 조합한 key가 더 안전합니다.

-                    depositList.map(item => (
+                    depositList.map((item) => (
                         <DepositBasic
-                            key={item.productId}
+                            key={`${item.productId}-${item.optionId}`}

백엔드 도메인 상 productId가 항상 유니크한지 확실치 않다면, 위와 같이 변경하는 쪽이 리스크가 적습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 886dfbf and 7d81bd6.

📒 Files selected for processing (2)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/savings-search/savings-search-page.tsx
🔇 Additional comments (1)
src/pages/deposit-search/deposit-search-page.tsx (1)

10-39: 은행 메타데이터/타입 구조 전반적으로 명확합니다.

ProductList 타입과 BANK_LIST 구성(식별용 id, 사용자 노출 name, 로고 컴포넌트 분리)이 명확해서 이후 필터링·렌더링 로직 이해하기 좋습니다. 현 시점에서 별도 수정 필요 없이 그대로 사용해도 좋아 보입니다.

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: 1

🧹 Nitpick comments (4)
src/pages/savings-search/savings-search-page.tsx (4)

10-19: ProductList 타입을 공용 타입으로 분리하는 걸 고려해 주세요

현재 이 파일 안에서만 ProductList를 정의하고 있는데, API 응답이나 SavingBasic 컴포넌트 props와 구조가 사실상 같을 가능성이 큽니다. 이미 비슷한 타입이 다른 곳에 있다면 여기서 새로 정의하기보다는 공용 타입으로 추출해서 import하는 편이 유지보수에 더 유리합니다 (필드 변경 시 한 군데만 수정하면 됨).


21-40: BANK_LIST의 id 값과 로고 매핑이 실제 스펙과 일치하는지 한 번 더 점검이 필요해 보입니다

  • id 값이 그대로 getSavingsListbankNames 파라미터로 사용되기 때문에, 백엔드에서 기대하는 은행 식별자와 정확히 일치하지 않으면 필터링이 제대로 되지 않을 수 있습니다.
  • BusanGyeongnamBankLogo, JeonbukGwangjuBankLogo 등 일부 로고 컴포넌트를 여러 은행에서 재사용하고 있는데, 디자인/브랜딩 측면에서 의도된 것인지 확인이 필요해 보입니다.
  • 동일한 BANK_LIST가 예금/적금 검색 페이지 모두에서 사용된다면, 추후에는 공용 상수(예: shared/constants/banks.ts)로 분리해 두면 중복을 줄일 수 있습니다.

65-69: useEffectfetchSavingList 의존성 관계를 명확히 하면 훅 린트 경고를 줄일 수 있습니다

현재 useEffect의 의존성 배열이 []인데, 내부에서 컴포넌트 스코프의 fetchSavingList를 호출하고 있어 react-hooks/exhaustive-deps 규칙이 켜져 있다면 경고가 날 수 있습니다.

fetchSavingListuseCallback으로 감싼 뒤:

const fetchSavingList = useCallback(async (banks: string[] | null, term: number) => {
  // ...
}, []);

useEffect에서는 [fetchSavingList]를 의존성으로 두는 방식으로 정리하면 의도가 더 명확해지고 린트 경고도 피할 수 있습니다.


100-148: 로딩 분기에서 JSX가 크게 중복되고, 로딩 상태 표시가 모호합니다

  • Line 100-148과 150-216의 필터 UI JSX가 거의 동일하게 두 번 선언되어 있어, 추후 UI 변경 시 두 군데를 모두 수정해야 하는 중복이 생깁니다.
  • 로딩 중에는 동일한 필터 화면만 보이고 상품 리스트 영역이 사라지기 때문에, 사용자가 “요청이 진행 중인지 / 단순히 결과가 없는 것인지”를 직관적으로 구분하기 어렵습니다.

아래처럼 구조를 바꾸면 중복을 줄이면서 로딩 상태도 더 명확히 표현할 수 있습니다.

-    if (loading) {
-        return(
-        <>
-        <Header/>
-        <main className={styles.mainContainer}>
-            {/* 은행/기간/검색 버튼 영역 ... (상품 리스트 없음) */}
-        </main>
-        </>
-            );
-    };
-
-    return (
-        <>
-        <Header/>
-        <main className={styles.mainContainer}>
-            {/* 은행/기간/검색 버튼 영역 ... */}
-            {/*상품 리스트*/}
-            <div className={styles.savingListContainer}>
-                {savingList.length > 0 ? (
-                    ...
-                ) : (
-                    <p>검색된 적금 상품이 없습니다.</p>
-                )}
-            </div>
-        </main>
-        </>
-    )};
+    return (
+        <>
+        <Header/>
+        <main className={styles.mainContainer}>
+            {/* 은행/기간/검색 버튼 영역 ... (한 번만 정의) */}
+            {/*상품 리스트*/}
+            <div className={styles.savingListContainer}>
+                {loading ? (
+                    <p>적금 상품을 불러오는 중입니다...</p>
+                ) : savingList.length > 0 ? (
+                    ...
+                ) : (
+                    <p>검색된 적금 상품이 없습니다.</p>
+                )}
+            </div>
+        </main>
+        </>
+    );

이렇게 하면

  • 필터 UI JSX 중복이 사라지고,
  • 로딩/결과 없음/정상 결과 상태를 한 곳에서 명확하게 분기할 수 있어 유지보수와 UX 모두 좋아질 것 같습니다.

Also applies to: 150-216

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d81bd6 and 33b2fcb.

📒 Files selected for processing (2)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/deposit-search/deposit-search-page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/savings-search/savings-search-page.tsx (5)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/pages/home/home-page.css.ts (1)
  • main (4-10)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/pages/deposit-search/deposit-search-page.css.ts (1)
  • section (15-20)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
🔇 Additional comments (2)
src/pages/savings-search/savings-search-page.tsx (2)

71-79: 은행 선택/전체 선택 토글 로직이 깔끔합니다

handleBankTogglehandleSelectAll 둘 다 불변 배열 패턴으로 잘 구현되어 있어, 상태 변경 흐름이 직관적이고 버그 여지가 적어 보입니다. 별다른 수정 없이 이대로 유지하셔도 좋겠습니다.

Also applies to: 93-99


83-91: 검색 처리에서 로딩 가드와 은행 목록 계산이 잘 정리되어 있습니다

  • if (loading) return;으로 로딩 중 중복 검색 호출을 막아, 이전에 지적되었던 응답 경쟁(race) 이슈를 잘 해소하신 것 같습니다.
  • selectedBanks가 비어 있을 때 BANK_LIST 전체를 한 번 targetBanks로 계산해 조회에 사용하고, 동시에 setSelectedBanks(targetBanks)로 UI 상태도 맞춰 주는 구조라 이후 유지보수 시 의도가 분명해 보입니다.

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: 0

♻️ Duplicate comments (3)
src/pages/savings-search/savings-search-page.tsx (3)

63-67: 초기 useEffect의 setIsLoading(true)null 파라미터는 조금 정리해 볼 수 있습니다

  • fetchSavingList 내부에서 이미 setIsLoading(true)를 호출하고 있어서, useEffect 안의 setIsLoading(true)는 기능적으로 중복입니다. 제거해도 동작에는 영향이 없습니다.
  • 초기 조회에서도 UI 상으로는 BANK_LIST 전체를 선택하고 있으니, fetchSavingList(null, DEFAULT_TERM) 대신 아래처럼 실제 API에도 동일한 리스트를 넘겨 주면 “선택된 은행”과 “요청 파라미터”가 항상 일치해 유지보수 관점에서 더 직관적입니다.
useEffect(() => {
    const allBankIds = BANK_LIST.map((b) => b.id);
    setSelectedBanks(allBankIds);
    fetchSavingList(allBankIds, DEFAULT_TERM);
}, []);

81-90: loading 가드와 함께 검색 버튼을 비활성화하면 중복 호출을 더 명확히 막을 수 있습니다

handleSearch에서 if (loading) return;으로 중복 검색 호출을 막은 점은 좋습니다.
UX 관점에서는 로딩 중 검색 버튼을 disabled 처리해 시각적으로도 “지금은 검색 중”이라는 상태를 보여 주면 더 명확합니다. (이 부분은 이전 리뷰에서 한번 언급되었던 내용이기도 합니다.)

예시:

     const handleSearch = () => {
         if (loading) return;
@@
-                <button
-                className={`${styles.searchButton} ${button({variant:'blue'})}`}
-                onClick={handleSearch}
-                >검색</button>
+                <button
+                    className={`${styles.searchButton} ${button({ variant: 'blue' })}`}
+                    onClick={handleSearch}
+                    disabled={loading}
+                >
+                    검색
+                </button>
@@
-                <button
-                className={`${styles.searchButton} ${button({variant:'blue'})}`}
-                onClick={handleSearch}
-                >검색</button>
+                <button
+                    className={`${styles.searchButton} ${button({ variant: 'blue' })}`}
+                    onClick={handleSearch}
+                    disabled={loading}
+                >
+                    검색
+                </button>

Also applies to: 137-141, 187-190


45-61: API 실패 시 초기 로딩/재검색 상황에서 모두 오해의 소지가 있는 UI 상태가 될 수 있습니다

현재 fetchSavingList에서 try/finally만 사용하고 있어:

  • 초기 진입 시 호출이 실패하면 savingList는 빈 배열 그대로인데, 로딩이 끝난 뒤 화면에는 단순히 “검색된 적금 상품이 없습니다.”가 표시됩니다.
  • 이후 검색에서 실패할 경우에는 이전 검색의 savingList가 그대로 남아서, 사용자는 “새 조건으로 검색했는데 결과가 안 바뀌는” 상황을 겪을 수 있습니다.
  • 에러 로그도 남지 않아 디버깅이 어렵습니다.

간단한 에러 상태를 추가해서 “정상적으로 결과가 0건인 경우”와 “네트워크/서버 에러인 경우”를 구분해 주는 쪽을 추천드립니다. 예시:

@@
-    const [selectedBanks, setSelectedBanks] = useState<string[]>([]);
-    const [saveTerm, setSaveTerm] = useState<number>(DEFAULT_TERM);  
-    
-    const fetchSavingList = async (banks: string[] | null, term: number) => {
-        setIsLoading(true);
-        try {
-            const res = await getSavingsList(banks || undefined, term);
-            const data: ProductList[] = [...(res?.result ?? [])].sort(
-                (a, b) => b.maxRate - a.maxRate,
-            );
-            setSavingList(data);
-        } finally {
-            setIsLoading(false);
-        }
-    };
+    const [selectedBanks, setSelectedBanks] = useState<string[]>([]);
+    const [saveTerm, setSaveTerm] = useState<number>(DEFAULT_TERM);
+    const [fetchError, setFetchError] = useState<string | null>(null);
+
+    const fetchSavingList = async (banks: string[] | null, term: number) => {
+        setIsLoading(true);
+        setFetchError(null);
+        try {
+            const res = await getSavingsList(banks || undefined, term);
+            const data: ProductList[] = [...(res?.result ?? [])].sort(
+                (a, b) => b.maxRate - a.maxRate,
+            );
+            setSavingList(data);
+        } catch (err) {
+            console.error('적금 상품 조회 실패:', err);
+            // 에러 시 이전 결과가 남지 않도록 초기화
+            setSavingList([]);
+            setFetchError('적금 상품 조회에 실패했습니다. 잠시 후 다시 시도해 주세요.');
+        } finally {
+            setIsLoading(false);
+        }
+    };
@@
-            <div className={styles.savingListContainer}>
-                {savingList.length > 0 ? (
+            <div className={styles.savingListContainer}>
+                {fetchError ? (
+                    <p>{fetchError}</p>
+                ) : savingList.length > 0 ? (
                     savingList.map(item => (
@@
-                ) : (
-                    <p>검색된 적금 상품이 없습니다.</p>
-                )}
+                ) : (
+                    <p>검색된 적금 상품이 없습니다.</p>
+                )}
             </div>

이렇게 하면:

  • 에러 시에는 명시적으로 “조회 실패” 메시지를 보여 줄 수 있고
  • 정상 응답 0건인 경우에만 “검색된 적금 상품이 없습니다.”가 노출되어 UX가 명확해집니다.

Also applies to: 193-210

🧹 Nitpick comments (3)
src/pages/savings-search/savings-search-page.tsx (3)

10-19: ProductList 타입을 공용 도메인 타입으로 분리하면 유지보수성이 더 좋아집니다

ProductList가 실제 API 응답과 SavingBasic 컴포넌트 props 구조와 동일하다면, 이 파일 내에서 새로 정의하기보다는 shared 쪽(예: API 타입 혹은 공용 도메인 타입 파일)에서 한 번 정의한 타입을 import 해서 재사용하는 편이 안전합니다.
향후 필드가 추가·변경될 때 타입 정의를 여러 곳에서 맞춰줘야 하는 중복을 줄일 수 있습니다.


21-40: BANK_LIST를 공통 상수로 분리하면 예·적금 페이지 간 중복과 불일치를 줄일 수 있습니다

여기 정의된 BANK_LIST는 순수한 메타데이터라서, 다른 페이지(예: 예금 검색, 인기 적금 카드 등)에서도 동일/유사하게 쓰이고 있을 가능성이 높습니다.
만약 실제로 여러 곳에서 같은 은행 리스트를 사용한다면, shared/constants/banks.ts 같은 위치로 분리해서 재사용하면:

  • 은행명/아이디/로고 변경 시 한 곳만 수정하면 되고
  • 일부 페이지에서 은행 누락/오타가 나는 리스크도 줄일 수 있습니다.

현재 구현 자체는 문제 없고, 구조 정리 측면에서의 제안입니다.


98-146: if (loading) 분기에서 전체 레이아웃을 복제하지 않고 공통 JSX를 공유하는 쪽이 깔끔합니다

현재 로딩 분기와 일반 렌더 분기에서 <Header />, <main>, 은행/기간/검색 버튼 섹션이 거의 동일하게 두 번 작성되어 있습니다. 이 구조는:

  • 스타일 수정이나 섹션 구조 변경 시 두 군데를 모두 수정해야 하고
  • 일부 수정이 한 군데에만 반영되면 미묘한 UI 차이가 날 수 있습니다.

if (loading) 분기를 제거하고, 항상 하나의 레이아웃만 렌더링한 뒤:

  • savingListContainer 안에서 loading, fetchError, 결과 유무에 따라
    • 스켈레톤/스피너
    • 에러 메시지
    • “검색된 적금 상품이 없습니다.”
    • 상품 리스트

를 조건부로 나누는 패턴을 쓰면 중복이 없어지고 가독성도 좋아집니다.

Also applies to: 148-214

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33b2fcb and 0c0007d.

📒 Files selected for processing (2)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/deposit-search/deposit-search-page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/savings-search/savings-search-page.tsx (5)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/pages/deposit-search/deposit-search-page.css.ts (1)
  • section (15-20)
src/shared/components/savings-basic/savings-basic.tsx (2)
  • SavingsBasicProps (15-57)
  • SavingsBasicProps (4-13)
src/shared/components/savings-popular/savings-popular.tsx (2)
  • SavingsPopularProps (12-40)
  • SavingsPopularProps (4-10)

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: 1

♻️ Duplicate comments (1)
src/pages/savings-search/savings-search-page.tsx (1)

50-61: API 오류가 발생하면 예외가 전파되어 컴포넌트가 중단될 수 있습니다.

현재 try-finally 구조만 있고 catch 블록이 완전히 없어서, API 호출 실패 시:

  • 오류가 처리되지 않고 상위로 전파됩니다
  • 사용자에게 오류 피드백이 제공되지 않습니다
  • 이전 검색 결과가 그대로 남아 혼란을 줄 수 있습니다

최소한 오류 로깅과 상태 초기화를 추가해야 합니다.

다음과 같이 수정하세요:

 const fetchSavingList = async (banks: string[] | null, term: number) => {
     setIsLoading(true);
     try {
         const res = await getSavingsList(banks || undefined, term);
         const data: ProductList[] = [...(res?.result ?? [])].sort(
             (a, b) => b.maxRate - a.maxRate,
         );
         setSavingList(data);
+    } catch (error) {
+        console.error('적금 상품 조회 실패:', error);
+        setSavingList([]);
     } finally {
         setIsLoading(false);
     }
 };

더 나은 사용자 경험을 위해 오류 상태를 추가하여 "조회에 실패했습니다" 메시지를 표시하는 것도 고려해 보세요.

🧹 Nitpick comments (2)
src/pages/savings-search/savings-search-page.tsx (2)

10-40: 공유 타입과 상수를 별도 파일로 분리하는 것을 고려해 보세요.

ProductList 타입과 BANK_LIST 상수가 다른 검색 페이지(예: deposit-search-page)에서도 사용될 가능성이 있습니다. 공통 타입과 상수를 src/shared/types 또는 src/shared/constants 디렉토리로 추출하면 코드 재사용성과 일관성이 향상됩니다.

또한 일부 은행이 동일한 로고를 공유하고 있습니다(예: 경남은행과 부산은행이 BusanGyeongnamBankLogo를 사용). 이것이 의도된 것인지 확인해 주세요.


81-90: 검색 로직을 단순화할 수 있습니다.

Lines 84-88의 로직이 약간 중복됩니다. targetBanks를 계산한 후 selectedBanks가 비어있는지 다시 확인하고 있습니다. 다음과 같이 단순화하는 것이 더 명확합니다:

 const handleSearch = () => {
     if (loading) return;
 
     const targetBanks = selectedBanks.length > 0 ? selectedBanks : BANK_LIST.map((b) => b.id);
 
     if (selectedBanks.length === 0) {
         setSelectedBanks(targetBanks);
     }
     fetchSavingList(targetBanks, saveTerm);
 };

또는 더 간결하게:

 const handleSearch = () => {
     if (loading) return;
 
-    const targetBanks = selectedBanks.length > 0 ? selectedBanks : BANK_LIST.map((b) => b.id);
-
-    if (selectedBanks.length === 0) {
-        setSelectedBanks(targetBanks);
-    }
-    fetchSavingList(targetBanks, saveTerm);
+    if (selectedBanks.length === 0) {
+        const allBanks = BANK_LIST.map((b) => b.id);
+        setSelectedBanks(allBanks);
+        fetchSavingList(allBanks, saveTerm);
+    } else {
+        fetchSavingList(selectedBanks, saveTerm);
+    }
 };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c0007d and ce4fa57.

📒 Files selected for processing (2)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/deposit-search/deposit-search-page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/savings-search/savings-search-page.tsx (3)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
🔇 Additional comments (1)
src/pages/savings-search/savings-search-page.tsx (1)

63-67: 초기화 로직이 올바릅니다.

컴포넌트 마운트 시 모든 은행을 선택하고 데이터를 가져오는 로직이 적절합니다. 빈 의존성 배열도 정확합니다.

@SKKU-FINITY SKKU-FINITY deleted a comment from coderabbitai bot Dec 1, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (7)
src/pages/deposit-search/deposit-search-page.tsx (7)

1-18: 타입 정의와 기본 구조는 무난합니다

ProductList 타입이 API 응답과 DepositBasic에 넘기는 props 구조를 잘 반영하고 있어서 전체 흐름이 이해하기 쉽습니다.
이미 공용 예·적금 상품 타입이 다른 모듈(예: products.ts나 공통 타입 모듈) 어딘가에 정의돼 있다면, 그쪽을 import해서 재사용하면 타입 중복을 줄일 수 있을 것 같습니다. 없다면 이대로 두셔도 괜찮습니다.


43-60: fetch 로직은 잘 구성돼 있지만, 에러 처리와 banks 파라미터 타입을 조금 더 정리할 여지가 있습니다

  • 정렬(maxRate 내림차순)과 로딩 플래그 on/off 흐름은 깔끔합니다.
  • 다만 getDepositsList가 실패하면 finally 덕분에 loading은 false로 내려가지만, 에러가 상위로 전파되면서 unhandled rejection 로그가 남을 수 있고, 사용자 입장에서는 단순히 “상품 없음” 상태로 오해할 수 있습니다.
  • fetchDepositListbanks 인자를 string[] | null로 받지만 실제 호출은 모두 string[]이어서, 타입을 단순화하거나 null을 명시적으로 처리해도 됩니다.

예를 들면 다음 정도의 보강을 고려할 수 있습니다:

-    const fetchDepositList = async (banks: string[] | null, term: number) => {
+    const fetchDepositList = async (banks: string[] | null, term: number) => {
       setIsLoading(true);
       try {
-            const res = await getDepositsList(banks || undefined, term);
+            const res = await getDepositsList(banks ?? undefined, term);
             const data: ProductList[] = [...(res?.result ?? [])].sort(
                 (a, b) => b.maxRate - a.maxRate,
             );
             setDepositList(data);
-        } finally {
+        } catch (error) {
+            // TODO: 공통 에러 처리 (토스트/에러 상태 등) 적용 시 이 부분에서 연결
+            console.error(error);
+        } finally {
             setIsLoading(false);
         }
     };

필수는 아니고, 추후 에러 UX를 설계할 때 같이 정리하면 좋을 것 같습니다.


62-66: 마운트 시 전체 은행 선택 + 기본 36개월 조회 흐름은 타당합니다

초기 마운트 시:

  • BANK_LIST에서 모든 id를 뽑아 selectedBanks를 채우고
  • 동일한 리스트/기간으로 바로 조회하는 흐름은 자연스럽고 UX 상 문제 없어 보입니다.

react-hooks/exhaustive-deps 규칙을 사용 중이라면, 이 useEffect에서 fetchDepositList 미포함으로 경고가 나올 수 있으니, 필요하면 void fetchDepositList(...) 형태로만 호출하고 lint 예외를 두는 등의 방식을 고려해 보셔도 좋겠습니다.


68-96: 은행 선택/전체선택/검색 로직 전반은 일관적이지만, UX 관점에서 몇 가지 선택지를 고민해볼 수 있습니다

좋은 점:

  • handleBankToggle이 함수형 업데이트를 사용해서 동시 클릭에도 안전하게 토글됩니다.
  • handleSearch에서 selectedBanks.length === 0이면 내부적으로 전체 은행을 대상으로 검색하고, 동시에 setSelectedBanks로 UI 상태도 전체 선택으로 맞춰주는 점은 일관성이 있습니다.
  • handleSelectAll이 “전부 선택되어 있으면 해제, 아니면 전부 선택” 토글을 제공하는 것도 자연스럽습니다.

고려해볼 점(선택 사항):

  • 현재 “전체선택” 버튼은 토글(전체 선택/전체 해제) 역할을 하지만, 라벨은 항상 “전체선택”으로 유지됩니다. 기획 의도에 따라 “전체선택/전체해제”를 상태에 따라 바꾸거나, 안내 문구로 “전체 선택 / 전체 해제 토글”임을 명확히 할지 한 번 더 논의해 보셔도 좋겠습니다.
  • 사용자가 일부러 은행을 모두 해제한 뒤 검색을 눌렀을 때, 자동으로 다시 전체 은행이 선택되는 동작(selectedBanks.length === 0 분기)이 직관적인지(=“은행을 하나도 선택 안 하면 전체 검색으로 간주한다”는 규칙) 기획과 UX 측면에서 한 번만 더 확인해 보시면 좋겠습니다.

로직 자체에는 치명적인 버그는 없어 보이고, 주로 UX 정책에 대한 선택의 문제라서, 현재 방식이 savings-search 페이지와 동일하다면 일단 그대로 가도 괜찮아 보입니다.


98-127: 은행 선택 버튼 JSX 구조는 괜찮지만, 접근성 측면에서 약간의 보강을 추천드립니다

현재 구조:

  • 버튼 + 아이콘 + 텍스트로 시각적으로는 충분히 상태가 구분됩니다.
  • 하지만 스크린 리더 기준으로는 “선택됨/선택 안 됨” 상태를 알기 어렵습니다.

작은 수정으로 접근성을 개선할 수 있습니다:

-                        {BANK_LIST.map((bank) => {
-                            const isSelected = selectedBanks.includes(bank.id);
-                            return (
-                                <button key={bank.id} className={`${styles.bankButton} ${isSelected ? styles.bankButtonSelected : ''}`}
-                                onClick={() => handleBankToggle(bank.id)}>
+                        {BANK_LIST.map((bank) => {
+                            const isSelected = selectedBanks.includes(bank.id);
+                            return (
+                                <button
+                                  key={bank.id}
+                                  type="button"
+                                  aria-pressed={isSelected}
+                                  className={`${styles.bankButton} ${isSelected ? styles.bankButtonSelected : ''}`}
+                                  onClick={() => handleBankToggle(bank.id)}
+                                >
  • type="button"은 혹시 나중에 이 섹션이 <form> 안으로 들어가더라도 예기치 않은 submit을 막아 줍니다.
  • aria-pressed는 토글 버튼 패턴에 맞는 속성으로, 현재 선택 상태를 보조 기술에 전달해 줍니다.

3-3: button CSS recipe import 이름을 조금 더 명확히 하면 가독성이 좋아질 수 있습니다

지금은 CSS recipe를 button이라는 이름으로 import해서, JSX의 <button> 태그와 이름이 겹쳐 약간 혼동될 수 있습니다.

-import { button } from '../../shared/components/button/button.css';
+import { button as buttonStyle } from '../../shared/components/button/button.css';
...
-                        className={`${styles.selectAllButton} ${button({variant:'pink'})}`}
+                        className={`${styles.selectAllButton} ${buttonStyle({ variant: 'pink' })}`}
...
-                className={`${styles.searchButton} ${button({variant:'pink'})}`}
+                className={`${styles.searchButton} ${buttonStyle({ variant: 'pink' })}`}

필수는 아니지만, 추후 유지보수 시 “이게 HTML 버튼인지 스타일 함수인지” 순간적으로 헷갈릴 여지를 줄여 줍니다.

Also applies to: 120-120, 138-138


144-163: 결과 리스트 렌더링 로직은 명확합니다

  • !loading일 때만 리스트/메시지를 보여주고, depositList.length에 따라 “상품 카드 목록” vs “검색된 예금 상품이 없습니다.” 분기하는 구조가 직관적입니다.
  • 위에서 정렬까지 해 주고 내려오기 때문에, 여기서는 렌더링에만 집중하고 있는 것도 좋습니다.

앞서 언급한 fetch 에러 처리만 보완된다면, “실패로 인한 빈 리스트”와 “정상 조회 결과 0건”을 구분해서 보여주는 것도 추후 UX 개선 포인트로 고려할 수 있겠습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1144491 and b74d69b.

📒 Files selected for processing (2)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/savings-search/savings-search-page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/deposit-search/deposit-search-page.tsx (4)
src/shared/api/products.ts (1)
  • getDepositsList (19-27)
src/pages/deposit-search/deposit-search-page.css.ts (1)
  • section (15-20)
src/shared/components/deposit-basic/deposit-basic.css.ts (1)
  • bank (15-19)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
🔇 Additional comments (2)
src/pages/deposit-search/deposit-search-page.tsx (2)

20-39: BANK_LIST의 id 값이 백엔드 bankNames 스펙과 정확히 맞는지 한 번 더 확인하는 게 좋겠습니다

이 리스트의 id 값이 그대로 getDepositsListbankNames 쿼리 파라미터로 전달되기 때문에, 문자열이 백엔드에서 기대하는 값과 조금만 달라도 해당 은행 상품이 전혀 조회되지 않을 수 있습니다.

  • 예: '국민은행' vs 'KB국민은행', '아이엠뱅크' 등 실제 API 스펙/샘플 응답과 비교 필요
  • 동일 패턴을 쓰는 savings-search 페이지의 BANK_LIST와도 값이 완전히 일치하는지 비교하면 안전합니다.

현재 코드 자체에는 즉시 보이는 버그는 없지만, 문자열 불일치가 있으면 디버깅이 까다로우니 스펙 기준으로 한 번 더 점검해 주세요.


129-142: 기간 DropDown과 검색 버튼 플로우는 자연스럽습니다 (타입 일치만 한 번 확인)

  • saveTermnumber 상태로 두고, <DropDown color='pink' value={saveTerm} onChange={handleTermChange} />로 제어형 컴포넌트로 사용한 부분은 의도에 잘 맞습니다.
  • handleSearch에서 disabled={loading}을 함께 사용한 것도 중복 검색 방지에 도움이 됩니다(완전히 막지는 못해도 UX 상 충분히 괜찮은 수준입니다).

한 가지 확인할 점:

  • DropDown 컴포넌트의 onChange가 실제로 number를 인자로 넘기도록 구현되어 있는지(내부에서 event.target.valueNumber(...)로 변환하는지) 타입/구현을 한 번만 더 체크해 주세요. 만약 string이라면 handleTermChange 시점에서 변환해 주는 편이 안전합니다.

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: 3

♻️ Duplicate comments (2)
src/pages/savings-search/savings-search-page.tsx (2)

140-146: 검색 버튼에 로딩 중 비활성화 처리가 누락되었습니다.

현재 검색 버튼에 disabled 속성이 없어서, 로딩 중에도 버튼이 시각적으로 활성화되어 있습니다. 사용자가 여러 번 클릭을 시도할 수 있으며, 로딩 상태가 명확하게 전달되지 않습니다.

다음과 같이 수정해주세요:

             <div className={styles.searchContainer}>
                 <button
                 className={`${styles.searchButton} ${button({variant:'blue'})}`}
                 onClick={handleSearch}
-                
+                disabled={loading}
                 >검색</button>
             </div>

51-64: finally 블록의 로직 오류로 검색 결과가 절대 표시되지 않습니다.

Lines 60-63의 finally 블록에서 setIsError(true)를 무조건 실행하고 있습니다. finally 블록은 성공/실패 여부와 관계없이 항상 실행되므로, API 호출이 성공해도 에러 상태가 true로 설정됩니다.

이로 인해 Line 148의 조건 !loading && !IsError가 항상 false가 되어 상품 리스트가 절대 표시되지 않습니다.

다음과 같이 catch 블록을 추가하고 finally 블록을 수정해주세요:

     const fetchSavingList = async (banks: string[] | null, term: number) => {
         setIsLoading(true);
         try {
             const res = await getSavingsList(banks || undefined, term);
             const data: ProductList[] = [...(res?.result ?? [])].sort(
                 (a, b) => b.maxRate - a.maxRate,
             );
             setSavingList(data);
             setIsError(false);
+        } catch (error) {
+            console.error('적금 상품 조회 실패:', error);
+            setSavingList([]);
+            setIsError(true);
         } finally {
             setIsLoading(false);
-            setIsError(true);
         }
     };
🧹 Nitpick comments (3)
src/pages/deposit-search/deposit-search-page.tsx (3)

10-39: 은행 상수 및 타입 정의 전반적으로 괜찮습니다만, 백엔드 키 값 재확인 부탁드립니다.

ProductList 타입 정의와 BANK_LIST 구성 자체는 명확합니다. 다만 Line 20-39의 id 값이 그대로 getDepositsListbankNames 쿼리 파라미터로 전달되므로, 백엔드에서 사용하는 은행명 키와 1:1로 일치하는지(특히 '아이엠뱅크', '한국스탠다드차타드은행'처럼 긴 문자열) 한 번만 더 확인해 주세요. 오타나 공백 차이만 있어도 특정 은행이 조회되지 않을 수 있습니다. 추가로, ProductList는 리스트가 아니라 개별 상품을 의미하므로 DepositProduct 같은 이름이 약간 더 직관적일 수 있습니다.


41-48: 기본 기간 상수와 DropDown 옵션/백엔드 지원 여부 확인 및 네이밍 정리 제안

Line 41-48에서 DEFAULT_TERM = 36으로 초기 조회 기간을 설정하고, Line 82-84에서 DropDownvalue={saveTerm}로 제어하는 흐름은 일관적입니다. 다만 DropDown 컴포넌트의 옵션에 실제로 36개월 값이 존재하는지, 그리고 백엔드가 term=36을 지원하는지 한 번만 확인해 두면 안전합니다. 또 변수명이 saveTerm이라 의미가 약간 모호해 보여서, term 또는 selectedTerm 정도로 바꾸면 이후 유지보수 시 읽기 더 편할 것 같습니다.

Also applies to: 82-84


50-71: fetchDepositList/검색 핸들러 로직은 안정적이지만, 타입/의존성·UX를 약간 다듬을 수 있습니다.

Line 50의 banks: string[] | null은 실제 호출부(Line 70, 93)를 보면 항상 string[]만 넘기고 있어 null 케이스가 불필요해 보입니다. banks: string[]로 단순화하고 그대로 getDepositsList(banks, term)에 전달하면 타입과 구현이 같이 깔끔해집니다. 또한 useEffect(Line 67-71)에서 fetchDepositList를 의존성 없이 사용하는 구조라 eslint exhaustive-deps를 쓰고 있다면 경고가 날 수 있으니, fetchDepositListuseCallback으로 감싸거나 effect 안에서 직접 API를 호출하는 형태로 바꾸는 것도 고려해 볼 만합니다. 마지막으로, 현재는 필터를 변경해도 검색 버튼을 누르기 전까지는 조회되지 않는 구조인데, 기획 의도가 “수동 검색(버튼 클릭 필수)”이 맞는지 한 번 확인해 보시면 좋겠습니다.

Also applies to: 85-101

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b74d69b and a4c522a.

📒 Files selected for processing (2)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/deposit-search/deposit-search-page.tsx (5)
src/shared/api/products.ts (1)
  • getDepositsList (19-27)
src/pages/home/home-page.css.ts (1)
  • main (4-10)
src/pages/deposit-search/deposit-search-page.css.ts (1)
  • section (15-20)
src/shared/components/deposit-basic/deposit-basic.css.ts (1)
  • bank (15-19)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
src/pages/savings-search/savings-search-page.tsx (3)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
🔇 Additional comments (5)
src/pages/savings-search/savings-search-page.tsx (4)

1-19: LGTM!

임포트 구문과 타입 정의가 적절합니다.


21-42: LGTM!

상수 정의가 명확하고 적절합니다.


72-100: LGTM!

이벤트 핸들러 로직이 올바르게 구현되어 있습니다. 로딩 중 중복 검색 방지 및 빈 은행 선택 처리가 적절합니다.


105-105: No changes required — styles.mainContainer is correct.

The CSS file exports the class as mainContainer (lowercase camelCase), which matches the current usage in line 105. The previous review's suggestion to use MainContainer was incorrect.

src/pages/deposit-search/deposit-search-page.tsx (1)

103-147: 은행 선택/기간/검색 UI 구조와 상태 제어는 직관적이고 잘 구성되어 있습니다.

Line 103-147의 Header + main 레이아웃, 은행 그리드 토글, 기간 DropDown, 검색 버튼까지 한 화면에 자연스럽게 연결되어 있어 사용 플로우가 명확합니다. button recipe 클래스와 styles.*를 조합해 버튼 스타일을 재사용한 점, 검색 버튼에 disabled={loading}을 걸어 중복 요청을 막은 부분도 구현이 깔끔합니다.

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: 1

♻️ Duplicate comments (1)
src/pages/savings-search/savings-search-page.tsx (1)

104-104: styles.mainContainer가 CSS 모듈 export 이름과 불일치할 가능성이 있습니다

이전에 정적 분석에서 mainContainer 대신 MainContainer를 제안한 코멘트가 있었는데, 현재도 Line 104에서 styles.mainContainer를 사용 중입니다. CSS 파일에서 아직 MainContainer로 export 하고 있다면 빌드/타입 에러가 계속 날 수 있습니다.

CSS 모듈의 실제 export 이름과 맞춰 주세요. 만약 MainContainer가 맞다면 이 파일은 다음처럼 수정되어야 합니다.

-        <main className={styles.mainContainer}>
+        <main className={styles.MainContainer}>

(반대로 CSS에서 mainContainer로 이미 변경했다면 이 코멘트는 무시하셔도 됩니다만, 한 번 더 확인 부탁드립니다.)

🧹 Nitpick comments (1)
src/pages/savings-search/savings-search-page.tsx (1)

147-172: 로딩 중 상품 영역이 완전히 비어 있어 사용자가 동작 여부를 파악하기 어렵습니다

현재는 loading === true일 때 상품 리스트 영역이 렌더링되지 않아, 검색 버튼을 눌러도 “아무 일도 안 일어나는 것처럼” 느껴질 수 있습니다.

필수는 아니지만, 아래처럼 리스트 영역 안에서 로딩/에러/결과를 한 번에 처리하면 UX가 더 자연스러울 것 같습니다.

-            {/*상품 리스트*/}
-            {!loading && !isError && (
-                <div className={styles.savingListContainer}>
-                    {savingList.length > 0 ? (
-                        ...
-                    ) : (
-                        <p>검색된 적금 상품이 없습니다.</p>
-                    )}
-                </div>
-            )}
-            {!loading && isError && (
-                <p>
-                    적금 상품을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.
-                </p>
-            )}
+            {/*상품 리스트*/}
+            <div className={styles.savingListContainer}>
+                {loading ? (
+                    <p>로딩 중입니다...</p>
+                ) : isError ? (
+                    <p>적금 상품을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.</p>
+                ) : savingList.length > 0 ? (
+                    savingList.map((item) => (
+                        <SavingBasic
+                            key={`${item.productId}-${item.optionId}`}
+                            ...
+                        />
+                    ))
+                ) : (
+                    <p>검색된 적금 상품이 없습니다.</p>
+                )}
+            </div>

이건 순수 UI 개선 포인트라, 우선순위에 따라 나중에 반영하셔도 괜찮습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5575f2c and a0b129c.

📒 Files selected for processing (1)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/savings-search/savings-search-page.tsx (3)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/shared/components/button/button.css.ts (1)
  • button (4-50)

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: 0

♻️ Duplicate comments (1)
src/pages/savings-search/savings-search-page.tsx (1)

51-65: 성공 시 에러 상태가 초기화되지 않아, 이전 에러 후 정상 조회해도 에러 메시지가 계속 표시됩니다

현재 catch 블록에서만 setIsError(true)를 호출하고 있는데, 성공 시 setIsError(false)를 호출하지 않아서 한 번 에러가 발생하면 이후 정상 조회 시에도 isError 상태가 그대로 남아있게 됩니다.

또한 이전 리뷰에서 제안된 에러 로깅(console.error)도 여전히 누락되어 있어 디버깅이 어렵습니다.

다음과 같이 수정하세요:

 const fetchSavingList = async (banks: string[] | null, term: number) => {
     setIsLoading(true);
+    setIsError(false);
     try {
         const res = await getSavingsList(banks || undefined, term);
         const data: ProductList[] = [...(res?.result ?? [])].sort(
             (a, b) => b.maxRate - a.maxRate,
         );
         setSavingList(data);
     } catch (error) {
+        console.error('적금 상품 조회 실패:', error);
         setSavingList([]);
         setIsError(true);
     } finally {
         setIsLoading(false);
     }
 };
🧹 Nitpick comments (1)
src/pages/savings-search/savings-search-page.tsx (1)

149-174: 로딩 중 사용자 피드백이 부족합니다

현재 loading 상태일 때는 Line 149와 Line 170의 조건문 모두 해당되지 않아, 상품 리스트 영역에 아무것도 표시되지 않습니다. 사용자는 검색 버튼이 비활성화된 것만 보고 로딩 중인지 알기 어렵습니다.

로딩 상태를 명시적으로 표시하도록 수정하는 것을 권장합니다:

             {/*상품 리스트*/}
-            {!loading && !isError && (
+            {loading ? (
+                <div className={styles.savingListContainer}>
+                    <p>상품을 불러오는 중입니다...</p>
+                </div>
+            ) : !isError ? (
                 <div className={styles.savingListContainer}>
                     {savingList.length > 0 ? (
                         savingList.map(item => (
                             <SavingBasic
                                 key={`${item.productId}-${item.optionId}`}
                                 productId={item.productId}
                                 optionId={item.optionId}
                                 bankName={item.bankName}
                                 productName={item.productName}
                                 rsrvTypeNm={item.rsrvTypeNm}
                                 saveTerm={item.saveTerm}
                                 baseRate={item.baseRate}
                                 maxRate={item.maxRate}
                             />
                         ))
                     ) : (
                         <p>검색된 적금 상품이 없습니다.</p>
                     )}
                 </div>
-            )}
-            {!loading && isError && (
+            ) : (
                 <p>
                     적금 상품을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.
                 </p>
             )}

필요하다면 로딩 스피너 컴포넌트를 사용해서 더 나은 UX를 제공할 수도 있습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0b129c and 8834e13.

📒 Files selected for processing (1)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/savings-search/savings-search-page.tsx (4)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/pages/home/home-page.css.ts (1)
  • main (4-10)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
🔇 Additional comments (1)
src/pages/savings-search/savings-search-page.tsx (1)

103-180: 전반적인 구조와 과거 이슈 수정사항을 확인했습니다

과거 리뷰에서 지적된 주요 이슈들(UI 중복, 로딩 경합 조건, 에러 상태 관리, 명명 규칙 등)이 대부분 잘 수정되었습니다. 컴포넌트 구조가 명확하고, 상태 관리와 핸들러 분리가 적절하며, 타입 안정성도 유지되고 있습니다.

위에서 언급한 에러 상태 초기화와 로딩 피드백 개선만 보완하시면 좋을 것 같습니다.

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: 0

🧹 Nitpick comments (2)
src/pages/deposit-search/deposit-search-page.tsx (2)

50-65: 정렬·에러 처리 로직은 좋은데, 에러 UI 레이아웃만 리스트와 맞춰두면 더 자연스러울 것 같습니다.

  • fetchDepositList에서 maxRate 기준 내림차순 정렬과 에러 상태 초기화까지 포함해서 데이터 흐름이 잘 정리돼 있습니다.
  • 다만 에러일 때(isError === true)는 Line 173-175에서 단순 <p>만 렌더링해서, 정상/빈 데이터일 때 사용하는 styles.depositListContainer와 레이아웃·여백이 달라질 수 있습니다.
  • 에러 메시지도 동일한 컨테이너 안에 넣으면 화면이 덜 튀고, 스크롤/패딩 일관성이 유지될 것 같습니다.

예시:

) : isError ? (
  <div className={styles.depositListContainer}>
    <p>예금 상품을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.</p>
  </div>
) : (
  ...
)

Also applies to: 149-176


67-71: '전체선택' / 무선택 시 동작을 조금 더 명확하게 해두면 UX가 좋아질 것 같습니다.

  • 마운트 시에는 모든 은행을 선택한 뒤 기본 조건으로 자동 조회하는 플로우는 괜찮습니다.
  • 이후 사용자가 모든 은행을 해제한 상태에서 검색을 누르면 selectedBanks.length === 0이어도 내부적으로 다시 전체 은행을 선택해 검색하는데, 사용자가 “정말 아무 은행도 선택하지 않겠다”는 의도였을 수도 있어 약간 헷갈릴 수 있습니다.
  • handleSelectAll은 전체 선택/해제 토글 역할을 하지만 버튼 라벨은 항상 “전체선택”으로만 보이는 점도 같이 헷갈릴 수 있는 부분입니다.
  • 기획이 “무선택 = 전체 검색”이라면, 라벨을 “전체 선택/해제”로 바꾸거나, 선택 개수를 함께 보여주는 등 의도를 더 드러내면 좋고,
    반대로 최소 1개를 반드시 선택하게 하려면 selectedBanks.length === 0에서 검색 시 경고 문구를 띄우고 요청을 막는 방향도 고려해 볼 만합니다.

Also applies to: 85-94, 95-101, 123-130

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8834e13 and b29cdc4.

📒 Files selected for processing (2)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/savings-search/savings-search-page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/deposit-search/deposit-search-page.tsx (4)
src/shared/api/products.ts (1)
  • getDepositsList (19-27)
src/pages/deposit-search/deposit-search-page.css.ts (1)
  • section (15-20)
src/shared/components/deposit-basic/deposit-basic.css.ts (1)
  • bank (15-19)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
🔇 Additional comments (1)
src/pages/deposit-search/deposit-search-page.tsx (1)

1-182: 전반적인 구조와 상태 관리가 명확해서 바로 사용 가능한 수준으로 보입니다.

  • 초기 진입 시 기본 조건(전체 은행 + DEFAULT_TERM)으로 자동 조회 후, 필터 변경 뒤 “검색” 버튼으로 재조회하는 흐름이 직관적입니다.
  • 로딩/에러/빈 결과 상태를 분리해 처리한 점도 좋고, 정렬 기준(maxRate 내림차순) 역시 요구사항에 부합해 보입니다.
  • 위에 남긴 소소한 UX 제안 정도만 고민해 보면 바로 머지해도 될 것 같습니다.

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: 0

🧹 Nitpick comments (5)
src/pages/deposit-search/deposit-search-page.css.ts (1)

1-132: 전체적으로 잘 구성된 CSS 모듈입니다.

Vanilla Extract 스타일 정의가 적절하게 구성되어 있고, 테마 변수를 일관되게 사용하고 있습니다. globalStyle을 통한 SVG 크기 조정도 적절합니다.

다만 70rem 값이 mainContainer, bankContainer, termContainer에서 반복되고 있어, 추후 유지보수를 위해 공통 상수로 추출하는 것도 고려해 볼 수 있습니다.

src/pages/savings-search/savings-search-page.tsx (3)

74-76: catch 블록에 에러 로깅을 추가하면 디버깅에 도움이 됩니다.

현재 에러 발생 시 상태만 업데이트하고 있어, 실제 에러 원인을 파악하기 어려울 수 있습니다.

     } catch (error) {
+      console.error('적금 상품 조회 실패:', error);
       setSavingList([]);
       setIsError(true);
     } finally {

144-144: 불필요한 화살표 함수 래퍼를 제거할 수 있습니다.

handleSelectAll은 파라미터를 받지 않으므로 직접 참조가 가능합니다.

-              <button
-                className={`${styles.selectAllButton} ${button({ variant: 'blue' })}`}
-                onClick={() => handleSelectAll()}
-              >
+              <button
+                className={`${styles.selectAllButton} ${button({ variant: 'blue' })}`}
+                onClick={handleSelectAll}
+              >

26-56: Extract BANK_LIST and ProductList type to a shared constants module.

These definitions are duplicated in both src/pages/deposit-search/deposit-search-page.tsx and src/pages/savings-search/savings-search-page.tsx. Consolidating them into a shared module (e.g., src/constants/bank.ts or src/types/product.ts) will reduce maintenance burden and ensure consistency.

src/pages/savings-search/savings-search-page.css.ts (1)

1-132: 예금 검색 페이지 CSS와 구조가 거의 동일합니다.

deposit-search-page.css.ts와 색상(blue vs pink)만 다르고 나머지 스타일 구조가 동일합니다. 현재 상태로도 동작에 문제는 없지만, 추후 공통 스타일을 추출하여 색상만 변수로 받는 방식으로 리팩토링하면 유지보수성을 높일 수 있습니다.

예시로 공통 스타일 팩토리를 만들 수 있습니다:

// shared/styles/search-page.css.ts
export const createSearchPageStyles = (colorScheme: 'blue' | 'pink') => {
  const primaryColor = colorScheme === 'blue' ? vars.color.blue300 : vars.color.pink300;
  const bgColor = colorScheme === 'blue' ? vars.color.blue100 : vars.color.pink100;
  // ... 공통 스타일 정의
};
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b29cdc4 and 8fcf341.

📒 Files selected for processing (5)
  • src/pages/deposit-search/deposit-search-page.css.ts (1 hunks)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.css.ts (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
  • src/styles/theme.css.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/deposit-search/deposit-search-page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/savings-search/savings-search-page.css.ts (2)
src/pages/deposit-search/deposit-search-page.css.ts (4)
  • bankButton (44-56)
  • bankButtonSelected (72-75)
  • selectAllButton (85-93)
  • searchButton (110-120)
src/styles/theme.css.ts (1)
  • vars (3-48)
🔇 Additional comments (3)
src/styles/theme.css.ts (1)

37-37: LGTM!

xxs 사이즈 토큰이 기존 사이즈 체계와 일관되게 추가되었습니다. xs(1.5rem)보다 작은 값(1.4rem)으로 적절하게 설정되어 있습니다.

src/pages/savings-search/savings-search-page.tsx (2)

60-80: 상태 관리와 에러 처리 로직이 잘 구현되어 있습니다.

loading, isError 상태를 적절히 활용하여 사용자 경험을 개선했고, try/catch/finally 패턴으로 안정적인 에러 처리가 구현되어 있습니다.


169-195: 조건부 렌더링이 명확하게 구현되어 있습니다.

로딩, 에러, 정상 상태를 삼항 연산자로 깔끔하게 분기 처리하여 사용자에게 적절한 피드백을 제공하고 있습니다.

Copy link
Member

@1jiwoo27 1jiwoo27 left a comment

Choose a reason for hiding this comment

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

  1. 상품 선택했다가 뒤로 가기 하면 검색 조건 (은행 버튼, 기간)이 초기화되는데, 유지하도록 하면 어떨까요?!
  2. 그리고 BANK_LIST는 따로 파일 만들어서 저장해두면 예금과 적금에서 불러쓰기 좋을 것 같아요!

나머지 리뷰는 이따 추가적으로 남길게요 ~~ 수고하셨어요 검색 잘 되네요 👍🏻👍🏻🫡

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: 0

🧹 Nitpick comments (1)
src/shared/components/dropdown/dropdown.tsx (1)

33-35: placeholder 렌더링 표현 간소화 및 truthy 체크 개선 제안

{value ? \${value}개월` : DEFAULT_PLACEHOLDER || DEFAULT_PLACEHOLDER}DEFAULT_PLACEHOLDER를 두 번 사용해 중복이고, value`를 truthy로만 체크해서 이론상 0일 때는 placeholder가 나옵니다.

도메인상 0개월이 들어올 일은 거의 없겠지만, 조금 더 명확하게 적으면 좋겠습니다.

- {value ? `${value}개월` : DEFAULT_PLACEHOLDER || DEFAULT_PLACEHOLDER}
+ {value != null ? `${value}개월` : DEFAULT_PLACEHOLDER}

이렇게 바꾸면 가독성이 좋아지고, 값이 0이어도 일관되게 레이블을 출력할 수 있습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e99439 and 34f5174.

📒 Files selected for processing (1)
  • src/shared/components/dropdown/dropdown.tsx (3 hunks)
🔇 Additional comments (2)
src/shared/components/dropdown/dropdown.tsx (2)

45-51: 외부 value 기반 isSelected 연동 👍

isSelected={option === value}로 선택 상태를 외부 value에만 의존하게 바꾼 부분이 제어형 컴포넌트 의도와 잘 맞습니다.
옵션 목록과 상위 상태가 일관되게 동기화될 수 있는 구조라서 괜찮아 보입니다.


11-24: All DropDown usages already provide both value and onChange props—no breaking changes detected.

Search found only 2 usages of the component, both in savings-search-page.tsx (line 155) and deposit-search-page.tsx (line 154), and both explicitly pass value and onChange. No uncontrolled usages exist that would silently break.

However, line 34 has a minor code quality issue: the placeholder expression DEFAULT_PLACEHOLDER || DEFAULT_PLACEHOLDER is redundant and should be simplified to just DEFAULT_PLACEHOLDER.

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: 0

🧹 Nitpick comments (5)
src/pages/deposit-search/deposit-search-page.tsx (3)

29-42: 예금 조회 실패 시 최소한의 로깅을 추가하면 디버깅에 도움이 됩니다

에러 시 리스트를 비우고 isError를 세팅해 UI에서 잘 분기하고 있어서 동작은 문제없습니다만, 운영 중 원인 분석을 위해 catch에서 간단한 로그 정도는 남기는 편이 좋습니다.

   const fetchDepositList = async (banks: string[] | null, term: number) => {
     setIsLoading(true);
     setIsError(false);
     try {
       const res = await getDepositsList(banks || undefined, term);
       const data: ProductList[] = [...(res?.result ?? [])].sort((a, b) => b.maxRate - a.maxRate);
       setDepositList(data);
     } catch (error) {
-      setDepositList([]);
-      setIsError(true);
+      console.error('예금 상품 조회 실패:', error);
+      setDepositList([]);
+      setIsError(true);
     } finally {
       setIsLoading(false);
     }
   };

63-71: ‘은행 미선택 시 자동 전체 선택’ 동작이 기획 의도인지 한 번만 확인해 주세요

selectedBanks.length === 0이면 전체 은행을 선택해 주고 그 목록으로 조회하는 로직은 “아무 것도 선택 안 하면 곧바로 전체 검색”이라는 UX로 이해됩니다. 일부 사용자에게는 “은행을 선택해 달라”는 검증 메시지를 기대할 수도 있어서, 현재 동작이 기획/디자인과 합의된 방향인지 한 번만 점검해 보시면 좋겠습니다. 필요하다면 이 부분을 분기해 “경고 메시지 + 조회 막기” 패턴으로 바꾸는 것도 가능합니다.


133-158: 에러 상태도 리스트 컨테이너 안에서 렌더링하면 레이아웃 일관성이 좋아집니다

지금은 loading/정상일 때는 styles.depositListContainer 안에서 메시지·리스트를 보여주고, 에러일 때만 맨 바깥에 <p>만 렌더링되어 세로 여백이나 정렬이 달라질 수 있습니다. 적금 페이지와 마찬가지로 에러 메시지도 동일 컨테이너 안에서 렌더링하면 레이아웃이 더 균일해집니다.

-        ) : (
-          <p>예금 상품을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.</p>
-        )}
+        ) : (
+          <div className={styles.depositListContainer}>
+            <p>예금 상품을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.</p>
+          </div>
+        )}
src/pages/savings-search/savings-search-page.tsx (2)

30-42: 예금/적금 fetch 로직이 완전히 동일해 공통 훅/유틸로 추출하기 좋습니다

fetchSavingList와 예금 쪽 fetchDepositList가 파라미터 이름과 API 함수만 다르고 구조가 완전히 동일합니다. 나중에 에러 처리나 정렬 기준을 바꾸게 되면 두 군데를 모두 손봐야 하니, useProductSearch 같은 공통 커스텀 훅이나 유틸 함수로 빼두면 유지보수 비용을 줄일 수 있을 것 같습니다. 이번 PR에서 꼭 하실 필요는 없지만, 이후 리팩터링 후보로 고려해 볼 만합니다.


81-132: Savings/Deposit 검색 페이지 레이아웃 공통 컴포넌트 도입 제안

은행 그리드, 기간 선택, 검색 버튼까지의 JSX 구조와 상태 사용 패턴이 예금/적금 페이지에서 거의 동일합니다(색상과 Basic 컴포넌트만 다름). 향후 필터 항목이 늘어나거나 UI 스펙이 바뀔 때 두 페이지를 각각 수정해야 하므로, 공통 ProductSearchLayout 컴포넌트(은행 선택 영역 + 기간 DropDown + 검색 버튼)를 만들어 색상/핸들러/Basic 컴포넌트만 props로 받는 구조로 정리해 두면 변경 비용과 UI 불일치 리스크를 줄일 수 있습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34f5174 and 351f646.

📒 Files selected for processing (3)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
  • src/shared/constants/bank-list.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/deposit-search/deposit-search-page.tsx (2)
src/shared/api/products.ts (1)
  • getDepositsList (19-27)
src/shared/constants/bank-list.ts (1)
  • BANK_LIST (25-48)
src/pages/savings-search/savings-search-page.tsx (6)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/shared/constants/bank-list.ts (1)
  • BANK_LIST (25-48)
src/pages/home/home-page.css.ts (1)
  • main (4-10)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/pages/deposit-search/deposit-search-page.css.ts (1)
  • section (15-20)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
🔇 Additional comments (1)
src/shared/constants/bank-list.ts (1)

19-48: BANK_LIST 중앙화 설계 좋습니다

은행 메타데이터(id·표시명·로고)를 한 곳에서 관리하도록 잘 정리돼 있어서 예금/적금 검색 페이지에서 일관성 있게 재사용하기 좋아 보입니다. 추후 백엔드에서 사용하는 bankNames 값이 변경될 수 있으니, 이 id 필드와 항상 동기화만 잘 챙겨주시면 될 것 같습니다.

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: 0

🧹 Nitpick comments (1)
src/pages/deposit-search/deposit-search-page.css.ts (1)

1-135: 공통 스타일 추출을 고려해 보세요.

이 파일은 savings-search-page.css.ts와 거의 동일하며, 색상 테마(핑크 vs 블루)만 다릅니다. 향후 유지보수를 위해 공통 스타일을 별도 모듈로 추출하고 색상만 파라미터화하는 것을 고려해 볼 수 있습니다.

예시 구조:

// shared/styles/search-page-base.css.ts
export const createSearchPageStyles = (themeColor: { primary: string; background: string }) => ({
  // 공통 스타일 정의
});

현재 구현도 정상 동작하므로 선택적 개선 사항입니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64b6c25 and 2c4a54b.

📒 Files selected for processing (4)
  • src/pages/deposit-search/deposit-search-page.css.ts (1 hunks)
  • src/pages/savings-search/savings-search-page.css.ts (1 hunks)
  • src/shared/components/deposit-popular/deposit-popular.tsx (1 hunks)
  • src/shared/components/savings-popular/savings-popular.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/savings-search/savings-search-page.css.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/deposit-search/deposit-search-page.css.ts (2)
src/pages/savings-search/savings-search-page.css.ts (14)
  • mainContainer (4-13)
  • section (15-20)
  • sectionTitle (22-27)
  • bankContainer (29-35)
  • bankGrid (37-43)
  • bankButton (45-57)
  • bankLogo (59-66)
  • bankName (68-72)
  • bankButtonSelected (74-77)
  • selectAllContainer (79-85)
  • selectAllButton (87-96)
  • termContainer (98-104)
  • searchContainer (106-111)
  • searchButton (113-123)
src/styles/theme.css.ts (1)
  • vars (3-48)
🔇 Additional comments (7)
src/shared/components/deposit-popular/deposit-popular.tsx (1)

32-32: AI 요약 앞 공백 추가로 가독성 개선

전구 이모지 뒤에 공백을 넣어 텍스트 가독성이 좋아졌고, 동일 패턴 컴포넌트들과의 UI 일관성도 맞춰져서 그대로 가져가면 될 것 같습니다.

src/shared/components/savings-popular/savings-popular.tsx (1)

36-36: 동일 패턴 컴포넌트와 UI 일관성 잘 맞춤

이모지 뒤 공백 추가로 deposit-popular와 시각적 스타일이 통일되었고, 기능 영향 없이 미세한 UI 품질만 개선되어 괜찮습니다.

src/pages/deposit-search/deposit-search-page.css.ts (5)

1-2: LGTM!

Vanilla Extract의 style, globalStyle 및 테마 변수를 올바르게 임포트하고 있습니다.


4-43: LGTM!

컨테이너 및 그리드 스타일이 기존 savings-search-page.css.ts와 일관된 패턴을 따르고 있습니다.


74-96: 핑크 테마 적용 확인.

예금 페이지에 핑크 색상 테마(pink100, pink300)를 적용하여 적금 페이지(블루 테마)와 시각적으로 구분한 점이 좋습니다. 사용자가 두 페이지를 쉽게 구별할 수 있습니다.


125-130: LGTM!

depositListContainer 스타일이 목록 아이템에 적절한 간격과 레이아웃을 제공합니다.


132-135: LGTM!

globalStyle을 사용하여 은행 로고 SVG가 컨테이너에 맞게 확장되도록 처리한 점이 좋습니다.

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: 1

🧹 Nitpick comments (5)
src/shared/stores/useDepositSearchStore.ts (1)

1-48: 선택적으로 두 검색 스토어의 중복 코드를 제거할 수 있습니다.

useDepositSearchStoreuseSavingsSearchStore가 거의 동일한 구조를 가지고 있습니다. 현재 코드는 명확하고 작동하지만, 향후 제네릭 팩토리 함수로 리팩토링하면 유지보수성을 높일 수 있습니다.

현재로서는 두 스토어의 타입이 약간 다르고(예: rsrvTypeNm 필드) 코드가 충분히 작아서 중복을 허용해도 괜찮습니다.

src/pages/savings-search/savings-search-page.tsx (2)

45-54: useEffect 의존성 배열이 불완전합니다.

useEffect가 initialized, setSelectedBanks, fetchSavingsList, setInitialized, saveTerm을 사용하지만 의존성 배열이 비어있습니다. Zustand 셀렉터는 안정적이므로 의도적인 것으로 보이지만, ESLint 경고가 발생할 수 있습니다.

다음 중 하나를 선택하세요:

방법 1: ESLint를 억제하고 의도를 명시

useEffect(() => {
  // Mount-only effect: initialize search with all banks
  if (!initialized) {
    const allBankIds = BANK_LIST.map((b) => b.id);
    setSelectedBanks(allBankIds);
    fetchSavingsList(allBankIds, saveTerm);
    setInitialized(true);
  } else {
    setIsLoading(false);
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

방법 2: 필요한 의존성을 추가 (store 셀렉터는 안정적이므로 안전)

}, [initialized, setSelectedBanks, setInitialized, saveTerm]);

163-165: 에러 메시지를 컨테이너 안으로 이동하세요.

에러 메시지가 savingListContainer 밖에 렌더링되어 일관된 스타일링이 적용되지 않을 수 있습니다.

다음과 같이 수정하면 더 일관된 레이아웃을 제공합니다:

        ) : (
+         <div className={styles.savingListContainer}>
            <p>적금 상품을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.</p>
+         </div>
        )}
src/pages/deposit-search/deposit-search-page.tsx (2)

46-55: useEffect 의존성 배열이 불완전합니다.

useEffect가 여러 값을 사용하지만 의존성 배열이 비어있어 ESLint 경고가 발생할 수 있습니다. savings-search-page.tsx와 동일한 패턴입니다.

다음 중 하나를 선택하세요:

방법 1: ESLint를 억제하고 의도를 명시

useEffect(() => {
  // Mount-only effect: initialize search with all banks
  if (!initialized) {
    const allBankIds = BANK_LIST.map((b) => b.id);
    setSelectedBanks(allBankIds);
    fetchDepositList(allBankIds, saveTerm);
    setInitialized(true);
  } else {
    setIsLoading(false);
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

방법 2: 필요한 의존성을 추가

}, [initialized, setSelectedBanks, setInitialized, saveTerm]);

165-165: 에러 메시지를 컨테이너 안으로 이동하세요.

에러 메시지가 depositListContainer 밖에 렌더링되어 일관된 스타일링이 적용되지 않을 수 있습니다.

다음과 같이 수정하면 더 일관된 레이아웃을 제공합니다:

        ) : (
+         <div className={styles.depositListContainer}>
            <p>예금 상품을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.</p>
+         </div>
        )}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c4a54b and 9fa3ab2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • package.json (1 hunks)
  • src/pages/deposit-search/deposit-search-page.tsx (1 hunks)
  • src/pages/savings-search/savings-search-page.tsx (1 hunks)
  • src/shared/stores/useDepositSearchStore.ts (1 hunks)
  • src/shared/stores/useSavingsSearchStore.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/savings-search/savings-search-page.tsx (4)
src/shared/stores/useSavingsSearchStore.ts (1)
  • useSavingsSearchStore (28-49)
src/shared/api/products.ts (1)
  • getSavingsList (4-12)
src/shared/constants/bank-list.ts (1)
  • BANK_LIST (25-48)
src/shared/components/savings-basic/savings-basic.tsx (2)
  • SavingsBasicProps (15-57)
  • SavingsBasicProps (4-13)
src/pages/deposit-search/deposit-search-page.tsx (8)
src/shared/stores/useDepositSearchStore.ts (1)
  • useDepositSearchStore (27-48)
src/shared/api/products.ts (1)
  • getDepositsList (19-27)
src/shared/constants/bank-list.ts (1)
  • BANK_LIST (25-48)
src/pages/home/home-page.css.ts (1)
  • main (4-10)
src/pages/deposit-search/deposit-search-page.css.ts (1)
  • section (15-20)
src/pages/savings-search/savings-search-page.css.ts (1)
  • section (15-20)
src/shared/components/deposit-basic/deposit-basic.css.ts (1)
  • bank (15-19)
src/shared/components/button/button.css.ts (1)
  • button (4-50)
🔇 Additional comments (4)
package.json (1)

22-22: LGTM!

Zustand는 가볍고 현대적인 상태 관리 라이브러리로, 검색 페이지의 상태 관리에 적합한 선택입니다.

src/shared/stores/useSavingsSearchStore.ts (1)

1-49: LGTM!

Zustand 스토어 구현이 깔끔하고 모범 사례를 잘 따르고 있습니다. 함수형 업데이트 패턴과 reset 기능이 잘 구현되어 있습니다.

src/shared/stores/useDepositSearchStore.ts (1)

1-48: LGTM!

구현이 적금 스토어와 일관된 패턴을 따르고 있어 유지보수에 좋습니다.

src/pages/deposit-search/deposit-search-page.tsx (1)

26-44: LGTM!

Line 31의 옵셔널 체이닝과 nullish coalescing 패턴 [...(res?.result ?? [])]이 안전하게 구현되어 있습니다. API 응답이 예상과 다를 때도 런타임 에러 없이 안전하게 처리됩니다.

Copy link
Member

@1jiwoo27 1jiwoo27 left a comment

Choose a reason for hiding this comment

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

전체 수정 완료 😴😴😴

@1jiwoo27 1jiwoo27 merged commit de67fa6 into develop Dec 1, 2025
2 checks passed
@1jiwoo27 1jiwoo27 deleted the feature/search-page/#31 branch December 1, 2025 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 예금 검색 페이지 구현

2 participants