Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1fb826d
feat: search page 구성
jihun3666 Nov 27, 2025
cec7754
feat: 폴더 및 파일이름 변경
jihun3666 Nov 27, 2025
3741c70
feat: 누락된 변경사항(index) 변경
jihun3666 Nov 28, 2025
bef816b
Merge remote-tracking branch 'origin/develop' into feature/search-pag…
jihun3666 Nov 28, 2025
4eff1a2
feat: 홈 페이지와 연결
jihun3666 Nov 28, 2025
886dfbf
feat: css파일 수정 및 수정사항 반영
jihun3666 Nov 29, 2025
7d81bd6
feat: 수정요청 반영(에러처리, 로딩중 검색)
jihun3666 Nov 29, 2025
33b2fcb
feat: 정렬 로직 수정, 기본값 상수화, 검색 로직 개선, 리스트 key 수정
jihun3666 Nov 29, 2025
0c0007d
feat: error 처리 부분 수정
jihun3666 Nov 30, 2025
ce4fa57
feat: useEffect 정리 및 검색버튼 disabled 처리 추가
jihun3666 Nov 30, 2025
1144491
feat: UI 코드 중복 제거
jihun3666 Dec 1, 2025
b74d69b
feat: loading 관련부분 수정
jihun3666 Dec 1, 2025
a4c522a
feat: 에러처리 추가
jihun3666 Dec 1, 2025
5575f2c
feat: 에러처리 수정
jihun3666 Dec 1, 2025
a0b129c
feat: 적금 로직 오류 수정 및 버튼 비활성화 누락 수정
jihun3666 Dec 1, 2025
8834e13
feat: savings 파일에 catch 추가
jihun3666 Dec 1, 2025
b29cdc4
feat: 로딩중 로직 추가
jihun3666 Dec 1, 2025
8fcf341
feat: 버튼 텍스트 사이즈 수정
1jiwoo27 Dec 1, 2025
8e99439
chore: pointer 추가
1jiwoo27 Dec 1, 2025
34f5174
chore: 주석 삭제
1jiwoo27 Dec 1, 2025
351f646
feat: bank list 분리
1jiwoo27 Dec 1, 2025
64b6c25
feat: 금액 안 잘리도록 수정
1jiwoo27 Dec 1, 2025
25635e8
chore: 띄어쓰기
1jiwoo27 Dec 1, 2025
2c4a54b
feat: css 수정
1jiwoo27 Dec 1, 2025
89b0471
feat: 상품 정렬 기준 변경
1jiwoo27 Dec 1, 2025
0077dcc
feat: 로딩 중일 떄 스피너 추가
1jiwoo27 Dec 1, 2025
91b0d21
feat: zustand 패키지 설치
1jiwoo27 Dec 1, 2025
9fa3ab2
feat: 검색 조건 유지되도록 수정
1jiwoo27 Dec 1, 2025
18b6ab7
fix: 옵셔널 체이닝 사용
1jiwoo27 Dec 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"axios": "^1.12.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.9.4"
"react-router-dom": "^7.9.4",
"zustand": "^5.0.9"
},
"devDependencies": {
"@eslint/js": "^9.37.0",
Expand Down
26 changes: 26 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

135 changes: 135 additions & 0 deletions src/pages/deposit-search/deposit-search-page.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { vars } from '../../styles/theme.css';
import { style, globalStyle } from '@vanilla-extract/css';

export const mainContainer = style({
display: 'block',
width: '70rem',
minHeight: '100vh',
marginLeft: 'auto',
marginRight: 'auto',
paddingBottom: '4rem',
boxSizing: 'border-box',
paddingTop: '10rem',
});

export const section = style({
display: 'flex',
flexDirection: 'column',
gap: '0.2rem',
marginBottom: '3rem',
});

export const sectionTitle = style({
fontSize: vars.size.lg,
fontWeight: vars.weight.semibold,
color: vars.color.black,
marginBottom: '1rem',
});

export const bankContainer = style({
display: 'flex',
flexDirection: 'column',
width: '70rem',
gap: '1.5rem',
borderRadius: '10px',
});

export const bankGrid = style({
display: 'grid',
gridTemplateColumns: 'repeat(6, 1fr)',
gap: '1rem',
justifyContent: 'center',
alignContent: 'center',
});

export const bankButton = style({
width: '100%',
aspectRatio: '1 / 1',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '0.5rem',
gap: '0.2rem',
border: `1px solid ${vars.color.gray500}`,
borderRadius: '5px',
cursor: 'pointer',
});

export const bankLogo = style({
width: '5rem',
height: '5rem',
marginBottom: '0.5rem',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});

export const bankName = style({
fontSize: vars.size.xxs,
fontWeight: vars.weight.medium,
color: vars.color.gray800,
});

export const bankButtonSelected = style({
border: `2px solid ${vars.color.pink300}`,
backgroundColor: vars.color.pink100,
});

export const selectAllContainer = style({
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
marginBottom: '1rem',
justifyContent: 'flex-end',
});

export const selectAllButton = style({
width: '11rem',
borderRadius: '5px',
cursor: 'pointer',
backgroundColor: vars.color.pink300,
fontSize: vars.size.sm,
fontWeight: vars.weight.semibold,
padding: '0.6rem 1rem',
border: 'none',
});

export const termContainer = style({
display: 'flex',
gap: '1rem',
width: '70rem',
alignItems: 'center',
justifyContent: 'center',
});

export const searchContainer = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
paddingBottom: '2rem',
});

export const searchButton = style({
backgroundColor: vars.color.pink300,
color: vars.color.white,
fontSize: vars.size.sm,
fontWeight: vars.weight.semibold,
padding: '0.6rem 1rem',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
width: '11rem',
});

export const depositListContainer = style({
display: 'flex',
flexDirection: 'column',
gap: '1rem',
marginTop: '2rem',
});

globalStyle(`${bankLogo} svg`, {
width: '100%',
height: '100%',
});
172 changes: 172 additions & 0 deletions src/pages/deposit-search/deposit-search-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { useState, useEffect } from 'react';
import { getDepositsList } from '../../shared/api/products';
import { useDepositSearchStore } from '../../shared/stores/useDepositSearchStore';
import { button } from '../../shared/components/button/button.css';
import { BANK_LIST } from '../../shared/constants/bank-list';
import Header from '../../shared/components/header/header';
import DepositBasic from '../../shared/components/deposit-basic/deposit-basic';
import DropDown from '../../shared/components/dropdown/dropdown';
import Spinner from '../../shared/components/spinner/spinner';
import * as styles from './deposit-search-page.css';

const DepositSearchPage = () => {
const {
selectedBanks,
saveTerm,
depositList,
initialized,
setSelectedBanks,
setSaveTerm,
setDepositList,
setInitialized,
} = useDepositSearchStore();
const [loading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);

const fetchDepositList = async (banks: string[], term: number) => {
setIsLoading(true);
setIsError(false);
try {
const res = await getDepositsList(banks, term);
const data = [...(res?.result ?? [])].sort((a, b) => {
if (a.saveTerm !== b.saveTerm) return b.saveTerm - a.saveTerm;
if (a.maxRate !== b.maxRate) return b.maxRate - a.maxRate;
return b.baseRate - a.baseRate;
});

setDepositList(data);
} catch (error) {
setDepositList([]);
setIsError(true);
} finally {
setIsLoading(false);
}
};

useEffect(() => {
if (!initialized) {
const allBankIds = BANK_LIST.map((b) => b.id);
setSelectedBanks(allBankIds);
fetchDepositList(allBankIds, saveTerm);
setInitialized(true);
} else {
setIsLoading(false);
}
}, []);

const handleBankToggle = (bankId: string) => {
setSelectedBanks((prev) => {
if (prev.includes(bankId)) {
return prev.filter((id) => id !== bankId);
} else {
return [...prev, bankId];
}
});
};

const handleTermChange = (term: number) => {
setSaveTerm(term);
};

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);
};

const handleSelectAll = () => {
if (selectedBanks.length === BANK_LIST.length) {
setSelectedBanks([]);
} else {
setSelectedBanks(BANK_LIST.map((b) => b.id));
}
};

return (
<>
<Header />
<main className={styles.mainContainer}>
{/*은행 선택*/}
<section className={styles.section}>
<h2 className={styles.sectionTitle}>은행</h2>
<div className={styles.bankContainer}>
<div className={styles.bankGrid}>
{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)}
>
<div className={styles.bankLogo}>
<bank.logo />
</div>
<span className={styles.bankName}>{bank.name}</span>
</button>
);
})}
</div>
<div className={styles.selectAllContainer}>
<button
className={`${styles.selectAllButton} ${button({ variant: 'pink' })}`}
onClick={() => handleSelectAll()}
>
전체선택
</button>
</div>
</div>
</section>
{/*기간 선택*/}
<section className={styles.section}>
<h2 className={styles.sectionTitle}>기간</h2>
<div className={styles.termContainer}>
<DropDown color='pink' value={saveTerm} onChange={handleTermChange} />
</div>
</section>
{/*검색 버튼*/}
<div className={styles.searchContainer}>
<button
className={`${styles.searchButton} ${button({ variant: 'pink' })}`}
onClick={handleSearch}
disabled={loading}
>
검색
</button>
</div>
{/*상품 리스트*/}
{loading ? (
<div className={styles.depositListContainer}>
<Spinner color='pink' />
</div>
) : !isError ? (
<div className={styles.depositListContainer}>
{depositList.length > 0 ? (
depositList.map((item) => (
<DepositBasic
key={`${item.productId}-${item.optionId}`}
productId={item.productId}
optionId={item.optionId}
bankName={item.bankName}
productName={item.productName}
saveTerm={item.saveTerm}
baseRate={item.baseRate}
maxRate={item.maxRate}
/>
))
) : (
<p>검색된 예금 상품이 없습니다.</p>
)}
</div>
) : (
<p>예금 상품을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.</p>
)}
</main>
</>
);
};

export default DepositSearchPage;
Loading