feat: 검색 메인 페이지#44
Conversation
Walkthrough여러 검색 관련 기능이 새롭게 추가되었습니다. 책 검색 페이지, 책 신청 페이지, 최근 검색어, 가장 많이 검색된 책 목록, 그리고 검색 결과 컴포넌트가 도입되었습니다. 일부 기존 컴포넌트의 이름과 동작도 변경되었으며, 라우팅 경로 및 조건부 렌더링 로직이 보완되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant SearchPage
participant BookSearchResult
participant MostSearchedBooks
participant ApplyBookPage
User->>SearchPage: 방문/검색어 입력
SearchPage->>MostSearchedBooks: (초기) 인기 책 목록 표시
User->>SearchPage: 검색어 입력 및 검색
SearchPage->>BookSearchResult: 검색 결과 요청 및 표시
User->>BookSearchResult: '책 신청하기' 클릭
BookSearchResult->>ApplyBookPage: 신청 페이지로 이동
Possibly related PRs
Suggested labels
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
src/pages/search/ApplyBook.tsx (1)
31-37: 높이 설정 개선 권장
height: 60vh사용 시 다양한 화면 크기에서 일관성 있는 사용자 경험을 제공하기 어려울 수 있습니다.min-height를 사용하거나 flex 속성을 활용하는 것을 고려해보세요.- height: 60vh; + flex: 1; + min-height: 300px;src/components/search/BookSearchResult.tsx (1)
13-16: 함수를 더 간결하게 리팩토링하세요.
isEmptySearchedBookList함수가 불필요하게 복잡합니다. 직접 boolean 값을 반환하는 것이 더 간결합니다.- const isEmptySearchedBookList = () => { - if (searchedBookList.length === 0) return true; - else return false; - }; + const isEmptySearchedBookList = () => searchedBookList.length === 0;또는 함수를 완전히 제거하고 인라인으로 사용할 수도 있습니다:
- {isEmptySearchedBookList() ? ( + {searchedBookList.length === 0 ? (src/pages/search/Search.tsx (2)
20-54: 더미 데이터를 별도 파일로 분리하거나 모킹 라이브러리 사용을 고려하세요.하드코딩된 더미 데이터가 컴포넌트 내부에 있어서 코드의 가독성을 떨어뜨리고 있습니다. 또한 외부 이미지 URL 사용은 서비스 안정성에 영향을 줄 수 있습니다.
다음과 같은 개선 방안을 고려해보세요:
- 더미 데이터를 별도 파일로 분리:
// src/mocks/searchData.ts export const mockSearchedBooks: SearchedBook[] = [...] export const mockRecentSearches: string[] = [...]
- 이미지 assets을 로컬에서 관리하거나 플레이스홀더 이미지 사용
- MSW 같은 모킹 라이브러리 도입 고려
117-127: 중복된 컴포넌트 렌더링을 단순화하세요.동일한 컴포넌트가
typeprop만 다르게 두 번 렌더링되고 있어 코드 중복이 발생하고 있습니다.다음과 같이 단순화할 수 있습니다:
- {isSearched ? ( - <BookSearchResult - type={'searched'} - searchedBookList={dummySearchedBook} - ></BookSearchResult> - ) : ( - <BookSearchResult - type={'searching'} - searchedBookList={dummySearchedBook} - ></BookSearchResult> - )} + <BookSearchResult + type={isSearched ? 'searched' : 'searching'} + searchedBookList={dummySearchedBook} + />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/components/common/Filter.tsx(1 hunks)src/components/common/NavBar.tsx(1 hunks)src/components/search/BookSearchResult.tsx(1 hunks)src/components/search/GroupSearchResult.tsx(2 hunks)src/components/search/MostSearchedBooks.tsx(1 hunks)src/components/search/SearchBar.tsx(2 hunks)src/pages/group/CreateGroup.tsx(1 hunks)src/pages/group/Group.tsx(1 hunks)src/pages/groupSearch/GroupSearch.tsx(2 hunks)src/pages/index.tsx(2 hunks)src/pages/search/ApplyBook.tsx(1 hunks)src/pages/search/Search.tsx(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/pages/search/ApplyBook.tsx (2)
src/components/common/Wrapper.tsx (1)
Wrapper(3-14)src/styles/global/global.ts (2)
colors(4-53)typography(56-76)
src/components/search/MostSearchedBooks.tsx (2)
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts (3)
BookList(166-170)BookItem(172-179)BookTitle(202-207)src/styles/global/global.ts (2)
typography(56-76)colors(4-53)
src/components/search/BookSearchResult.tsx (2)
src/pages/search/Search.tsx (1)
SearchedBook(12-18)src/styles/global/global.ts (2)
colors(4-53)typography(56-76)
🔇 Additional comments (16)
src/pages/group/CreateGroup.tsx (1)
11-11: LGTM!에셋 파일의 구조 개선을 위한 경로 변경으로 다른 새로운 컴포넌트들과 일관성을 유지하고 있습니다.
src/pages/search/ApplyBook.tsx (1)
8-27: 컴포넌트 구조 및 로직 양호컴포넌트의 전체적인 구조와 네비게이션 로직이 적절하게 구현되어 있습니다.
src/pages/group/Group.tsx (1)
132-132: LGTM!라우팅 경로를 소문자로 통일하여 웹 표준에 맞게 개선되었습니다.
src/pages/groupSearch/GroupSearch.tsx (2)
8-8: LGTM!컴포넌트명을 더 명확하고 구체적으로 변경하여 코드의 가독성이 향상되었습니다.
60-60: LGTM!import 변경과 일치하는 컴포넌트 사용으로 일관성이 유지되고 있습니다.
src/components/search/GroupSearchResult.tsx (1)
87-87: LGTM!컴포넌트명과 export를 일관성 있게 변경하여 다른 파일들의 import와 완벽히 일치합니다.
Also applies to: 142-142
src/components/common/Filter.tsx (1)
23-23: 모달 토글 동작 개선이 적절합니다.기존의 조건부 열기에서 직접 토글로 변경하여 사용자가 필터를 다시 클릭해서 모달을 닫을 수 있게 되었습니다. 이는 일반적인 드롭다운/모달 컴포넌트의 예상 동작입니다.
src/components/common/NavBar.tsx (1)
93-93: 방어적 프로그래밍으로 안전성이 향상되었습니다.path prop이 falsy일 때 Fab 컴포넌트를 렌더링하지 않도록 하여 잠재적인 오류를 방지합니다.
src/pages/index.tsx (2)
15-17: import 경로 수정 및 새로운 컴포넌트 추가가 적절합니다.GroupSearch import 경로의 대소문자가 수정되었고, 검색 기능을 위한 새로운 컴포넌트들이 올바르게 import되었습니다.
33-34: 검색 기능을 위한 라우팅 확장이 올바릅니다.새로운 검색 페이지와 도서 신청 페이지에 대한 라우트가 적절히 추가되었습니다.
src/components/search/SearchBar.tsx (2)
12-12: 검색 상태 관리를 위한 prop 추가가 적절합니다.
isSearchedprop 추가로 검색 후 UI 동작을 제어할 수 있게 되었습니다.Also applies to: 15-22
36-38: 검색 후 삭제 버튼 숨김 로직이 UX를 개선합니다.검색이 완료된 후에는 삭제 버튼을 숨겨서 사용자 혼란을 방지하는 좋은 UX 개선입니다.
src/components/search/MostSearchedBooks.tsx (1)
98-109: 접근성 개선을 위한 시맨틱 HTML 사용이 좋습니다.BookList를
ul로, BookItem을li로 사용하여 스크린 리더 접근성이 개선되었습니다. 순위 목록의 의미가 명확하게 전달됩니다.src/components/search/BookSearchResult.tsx (2)
21-48: 렌더링 로직이 잘 구현되었습니다.조건부 렌더링, 빈 상태 처리, 그리고 접근성을 위한 alt 텍스트 제공이 모두 적절하게 구현되어 있습니다.
50-132: 스타일링이 디자인 시스템과 일관되게 구현되었습니다.colors와 typography 상수를 적절히 활용하여 일관된 스타일링을 구현했습니다.
src/pages/search/Search.tsx (1)
148-188: 레이아웃과 스타일링이 적절하게 구현되었습니다.fixed positioning과 z-index를 활용한 레이어링, 반응형 디자인 고려가 모두 적절합니다.
| onLeftClick={handleBackButton} | ||
| /> | ||
| <TextWrapper> | ||
| <MainText>texthip2025@gmail.com</MainText> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
하드코딩된 이메일 주소 개선 필요
이메일 주소가 하드코딩되어 있어 향후 변경 시 코드 수정이 필요합니다. 환경 변수나 설정 파일로 관리하는 것을 고려해보세요.
- <MainText>texthip2025@gmail.com</MainText>
+ <MainText>{process.env.REACT_APP_SUPPORT_EMAIL || 'texthip2025@gmail.com'}</MainText>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <MainText>texthip2025@gmail.com</MainText> | |
| <MainText>{process.env.REACT_APP_SUPPORT_EMAIL || 'texthip2025@gmail.com'}</MainText> |
🤖 Prompt for AI Agents
In src/pages/search/ApplyBook.tsx at line 21, the email address is hardcoded
directly in the JSX. To improve maintainability, replace the hardcoded email
with a value sourced from an environment variable or a configuration file.
Update the code to read the email from process.env or a config object and use
that variable in the JSX instead of the fixed string.
| const Cover = styled.img` | ||
| width: 45px; | ||
| height: 60px; | ||
| object-fit: cover; | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
이미지 로딩 실패 처리를 추가해야 합니다.
외부 이미지 URL 사용 시 로딩 실패에 대한 fallback 처리가 필요합니다.
다음과 같이 개선하는 것을 권장합니다:
const Cover = styled.img`
width: 45px;
height: 60px;
object-fit: cover;
+ background-color: ${colors.grey[400]};
`;그리고 컴포넌트에서 onError 핸들러를 추가:
<Cover
src={book.coverUrl}
alt={`${book.title} 커버`}
onError={(e) => {
e.currentTarget.src = '/images/book-placeholder.jpg';
}}
/>🤖 Prompt for AI Agents
In src/components/search/MostSearchedBooks.tsx around lines 118 to 122, the
Cover styled image component lacks error handling for failed image loads. To fix
this, add an onError event handler to the Cover component usage that sets the
image source to a local fallback image (e.g., '/images/book-placeholder.jpg')
when the original image fails to load. This ensures a graceful fallback for
broken external image URLs.
| {/* 서버 응답 포맷을 모르기에 우선 하드 코딩 */} | ||
| <DateText>01.12. 기준</DateText> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
하드코딩된 날짜를 동적으로 처리하도록 개선이 필요합니다.
현재 하드코딩된 날짜는 임시 구현으로 보입니다. 프로덕션에서는 서버에서 받은 데이터를 기반으로 동적으로 표시되어야 합니다.
다음과 같이 개선하는 것을 권장합니다:
- {/* 서버 응답 포맷을 모르기에 우선 하드 코딩 */}
- <DateText>01.12. 기준</DateText>
+ <DateText>{formatDate(lastUpdated)} 기준</DateText>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/search/MostSearchedBooks.tsx around lines 48 to 49, the date
"01.12." is hardcoded in the DateText component. Replace this hardcoded date
with a dynamic value sourced from the server response or component props. Ensure
the date is formatted correctly before rendering, so it updates based on actual
data rather than a fixed string.
| const dummyBooks: Book[] = [ | ||
| { | ||
| id: 1, | ||
| title: '토마토 컵라면', | ||
| coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg', | ||
| }, | ||
| { | ||
| id: 2, | ||
| title: '사슴', | ||
| coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg', | ||
| }, | ||
| { | ||
| id: 3, | ||
| title: '호르몬 체인지지', | ||
| coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg', | ||
| }, | ||
| { | ||
| id: 4, | ||
| title: '호르몬 체인지지', | ||
| coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg', | ||
| }, | ||
| { | ||
| id: 5, | ||
| title: '호르몬 체인지지', | ||
| coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg', | ||
| }, | ||
| { | ||
| id: 6, | ||
| title: '호르몬 체인지지', | ||
| coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg', | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
더미 데이터에서 보안 및 데이터 품질 문제가 있습니다.
- 외부 URL 사용으로 인한 보안 및 가용성 위험
- 3번부터 6번까지 동일한 책 제목으로 복사-붙여넣기 오류 의심
- 모든 책이 동일한 외부 이미지 URL 사용
다음과 같이 개선하는 것을 권장합니다:
const dummyBooks: Book[] = [
{
id: 1,
title: '토마토 컵라면',
- coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg',
+ coverUrl: '/images/book-placeholder.jpg',
},
{
id: 2,
- title: '사슴',
- coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg',
+ title: '어린 왕자',
+ coverUrl: '/images/book-placeholder-2.jpg',
},
{
id: 3,
- title: '호르몬 체인지지',
- coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg',
+ title: '1984',
+ coverUrl: '/images/book-placeholder-3.jpg',
},
// 각각 다른 책 정보로 수정
];Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/search/MostSearchedBooks.tsx between lines 10 and 41, the
dummyBooks array has security and data quality issues: it uses external image
URLs which pose security and availability risks, and books from id 3 to 6 have
duplicated titles and identical cover URLs likely due to copy-paste errors. To
fix this, replace the external image URLs with local or trusted internal assets,
ensure each book has a unique and meaningful title, and assign distinct cover
images for each book to improve data quality and security.
| <Content> | ||
| {isSearching ? ( | ||
| <> | ||
| ( |
There was a problem hiding this comment.
문법 오류: 불필요한 괄호를 제거하세요.
JSX 내에 의미 없는 괄호가 있어 문법 오류를 발생시킬 수 있습니다.
다음과 같이 수정하세요:
- (
{isSearched ? (
<BookSearchResult
type={'searched'}
searchedBookList={dummySearchedBook}
></BookSearchResult>
) : (
<BookSearchResult
type={'searching'}
searchedBookList={dummySearchedBook}
></BookSearchResult>
)}
- )Also applies to: 128-128
🤖 Prompt for AI Agents
In src/pages/search/Search.tsx at lines 116 and 128, there are unnecessary
parentheses in the JSX code that cause syntax errors. Remove these redundant
parentheses to correct the JSX syntax and ensure the component renders properly.
| const [searchTerm, setSearchTerm] = useState(''); | ||
| const [isSearching, setIsSearching] = useState(false); | ||
| const [isSearched, setIsSearched] = useState(false); | ||
|
|
||
| const [recentSearches, setRecentSearches] = useState<string[]>([ | ||
| '딸기12', | ||
| '당근', | ||
| '수박245', | ||
| '참', | ||
| '메론1', | ||
| ]); | ||
|
|
||
| const handleChange = (value: string) => { | ||
| setSearchTerm(value); | ||
| setIsSearched(false); | ||
| setIsSearching(value.trim() !== ''); | ||
| }; | ||
|
|
||
| const handleSearch = (term: string) => { | ||
| if (!term.trim()) return; | ||
| setIsSearching(true); | ||
| setIsSearched(true); | ||
| setRecentSearches(prev => { | ||
| const filtered = prev.filter(t => t !== term); | ||
| return [term, ...filtered].slice(0, 5); | ||
| }); | ||
| }; | ||
|
|
||
| const handleDelete = (recentSearch: string) => { | ||
| setRecentSearches(prev => prev.filter(t => t !== recentSearch)); | ||
| }; | ||
|
|
||
| const handleRecentSearchClick = (recentSearch: string) => { | ||
| setSearchTerm(recentSearch); | ||
| setIsSearched(true); | ||
| setIsSearching(true); | ||
| }; | ||
|
|
||
| const handleBackButton = () => { | ||
| setSearchTerm(''); | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| if (searchTerm.trim() === '') { | ||
| setIsSearching(false); | ||
| setIsSearched(false); | ||
| } | ||
| }, [searchTerm]); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
복잡한 상태 관리 로직을 useReducer로 리팩토링하는 것을 고려하세요.
현재 여러 개의 연관된 상태가 분산되어 관리되고 있어 복잡도가 높습니다. 상태 간의 의존성으로 인한 버그 발생 가능성이 있습니다.
useReducer를 사용한 상태 관리 패턴을 고려해보세요:
type SearchState = {
searchTerm: string;
isSearching: boolean;
isSearched: boolean;
recentSearches: string[];
};
type SearchAction =
| { type: 'SET_SEARCH_TERM'; payload: string }
| { type: 'SUBMIT_SEARCH'; payload: string }
| { type: 'CLEAR_SEARCH' }
| { type: 'SELECT_RECENT_SEARCH'; payload: string };
function searchReducer(state: SearchState, action: SearchAction): SearchState {
// 상태 업데이트 로직을 중앙화
}이렇게 하면 상태 변화를 더 예측 가능하고 관리하기 쉽게 만들 수 있습니다.
🤖 Prompt for AI Agents
In src/pages/search/Search.tsx between lines 44 and 91, the current state
management uses multiple useState hooks for related states, causing complexity
and potential bugs. Refactor this by replacing all these useState calls with a
single useReducer hook that manages an object containing searchTerm,
isSearching, isSearched, and recentSearches. Define a reducer function that
handles actions like SET_SEARCH_TERM, SUBMIT_SEARCH, CLEAR_SEARCH, and
SELECT_RECENT_SEARCH to update the state accordingly. Update all handlers to
dispatch these actions instead of setting state directly, centralizing and
simplifying state logic.
|
큰차이 없을거같기는한데... 조건부 렌더링부분을 함수로 빼내서 return() 부분에 넣으면 렌더링 영역의 구조에 대한 가독성은 좀 높아질거같은데 별로일까여? |
정확히 어디부분 말씀하시는지 모르겠네요.. 그 리뷰 남길때 코드에 마우스 올리면 버튼나오는데 누르고 드래그하면 리뷰 남기고 싶은 부분 지정 가능합니다! |
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
#️⃣연관된 이슈
[UI] 검색 메인 페이지 #38
📝작업 내용
스크린샷 (선택)
검색 메인 페이지는 검색 창 값에 따라 뷰가 변화합니다.
검색창에 값이 없을때
Summary by CodeRabbit
신규 기능
버그 수정
개선 사항