diff --git a/src/components/common/Filter.tsx b/src/components/common/Filter.tsx index 6afab5f2..e3023c45 100644 --- a/src/components/common/Filter.tsx +++ b/src/components/common/Filter.tsx @@ -20,9 +20,7 @@ export const Filter = ({ filters, selectedFilter, setSelectedFilter }: FilterPro }; const handleModalClick = () => { - if (!isOpenModal) { - setIsOpenModal(true); - } + setIsOpenModal(!isOpenModal); }; return ( diff --git a/src/components/common/NavBar.tsx b/src/components/common/NavBar.tsx index b6a6bb54..028e0471 100644 --- a/src/components/common/NavBar.tsx +++ b/src/components/common/NavBar.tsx @@ -90,7 +90,7 @@ const NavBar = ({ src, path }: FabProps) => { ); })} - + {path && } ); diff --git a/src/components/search/BookSearchResult.tsx b/src/components/search/BookSearchResult.tsx new file mode 100644 index 00000000..39b26624 --- /dev/null +++ b/src/components/search/BookSearchResult.tsx @@ -0,0 +1,132 @@ +import type { SearchedBook } from '@/pages/search/Search'; +import styled from '@emotion/styled'; +import { colors, typography } from '@/styles/global/global'; +import { useNavigate } from 'react-router-dom'; + +interface BookSearchResultProps { + type: 'searching' | 'searched'; + searchedBookList: SearchedBook[]; +} + +export function BookSearchResult({ type, searchedBookList }: BookSearchResultProps) { + const navigate = useNavigate(); + const isEmptySearchedBookList = () => { + if (searchedBookList.length === 0) return true; + else return false; + }; + + const handleApplyBook = () => { + navigate('/search/applybook'); + }; + return ( + + + {type === 'searching' ? <> : 전체 {searchedBookList.length}} + + {isEmptySearchedBookList() ? ( + + 현재 등록된 책이 없어요. + 원하는 책을 신청해주세요! + 책 신청하기 + + ) : ( + searchedBookList.map(book => ( + + + + {book.title} + + {book.author} 저 · {book.publisher} + + + + )) + )} + + + ); +} + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + padding: 0 20px; + margin-bottom: 72px; +`; + +const List = styled.div` + display: flex; + flex-direction: column; +`; + +const BookItem = styled.div` + display: flex; + border-bottom: 1px solid ${colors.darkgrey.dark}; + padding: 12px 0; +`; + +const ResultHeader = styled.div` + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.medium}; + color: ${colors.white}; + padding-bottom: 8px; + border-bottom: 1px solid ${colors.darkgrey.dark}; +`; + +const Cover = styled.img` + width: 80px; + height: 107px; + object-fit: cover; +`; + +const BookInfo = styled.div` + display: flex; + flex-direction: column; + margin-left: 12px; +`; + +const Title = styled.h3` + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.semibold}; + color: ${colors.white}; +`; + +const Subtitle = styled.span` + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; + color: ${colors.grey[200]}; + margin-top: 8px; +`; + +const EmptyWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 60vh; +`; + +const MainText = styled.p` + color: ${colors.white}; + font-size: ${typography.fontSize.lg}; + font-weight: ${typography.fontWeight.semibold}; + margin-bottom: 8px; +`; + +const SubText = styled.p` + color: ${colors.grey[100]}; + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.regular}; + margin-bottom: 20px; +`; + +const RequestButton = styled.button` + background-color: ${colors.purple.main}; + color: ${colors.white}; + padding: 10px 12px; + font-size: ${typography.fontSize.base}; + border: none; + border-radius: 12px; + font-weight: ${typography.fontWeight.semibold}; + cursor: pointer; +`; diff --git a/src/components/search/SearchResult.tsx b/src/components/search/GroupSearchResult.tsx similarity index 98% rename from src/components/search/SearchResult.tsx rename to src/components/search/GroupSearchResult.tsx index 898f4343..1c07f27b 100644 --- a/src/components/search/SearchResult.tsx +++ b/src/components/search/GroupSearchResult.tsx @@ -84,7 +84,7 @@ const dummyMyGroups: Group[] = [ }, ]; -const SearchResult = () => { +const GroupSearchResult = () => { const [selected, setSelected] = useState(''); const [showGroup] = useState(dummyMyGroups); const [selectedFilter, setSelectedFilter] = useState('마감임박순'); @@ -139,7 +139,7 @@ const SearchResult = () => { ); }; -export default SearchResult; +export default GroupSearchResult; const TabContainer = styled.div` display: flex; diff --git a/src/components/search/MostSearchedBooks.tsx b/src/components/search/MostSearchedBooks.tsx new file mode 100644 index 00000000..a485b25c --- /dev/null +++ b/src/components/search/MostSearchedBooks.tsx @@ -0,0 +1,153 @@ +import { colors, typography } from '@/styles/global/global'; +import styled from '@emotion/styled'; + +interface Book { + id: number; + title: string; + coverUrl: 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', + }, +]; + +export default function MostSearchedBooks() { + return ( + +
+ 가장 많이 검색된 책 + {/* 서버 응답 포맷을 모르기에 우선 하드 코딩 */} + 01.12. 기준 +
+ {dummyBooks.length === 0 ? ( + + 아직 순위가 집계되지 않았어요. + 조금만 기다려주세요! + + ) : ( + + {dummyBooks.map((book, index) => ( + + {index + 1}. + + {book.title} + + ))} + + )} +
+ ); +} + +const Container = styled.div` + display: flex; + flex-direction: column; + padding: 36px 20px 0 20px; + background-color: var(--color-black-main); + margin-bottom: 72px; +`; + +const Header = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +`; + +const Title = styled.h2` + font-size: ${typography.fontSize.lg}; + color: ${colors.white}; + font-weight: ${typography.fontWeight.semibold}; +`; + +const DateText = styled.span` + font-size: ${typography.fontSize.xs}; + color: ${colors.grey[300]}; + font-weight: ${typography.fontWeight.regular}; +`; + +const BookList = styled.ul` + display: flex; + flex-direction: column; + margin-top: 4px; +`; + +const BookItem = styled.li` + display: flex; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid ${colors.darkgrey.dark}; +`; + +const Rank = styled.span` + font-size: ${typography.fontSize.base}; + color: ${colors.white}; + font-weight: ${typography.fontWeight.medium}; + width: 24px; +`; + +const Cover = styled.img` + width: 45px; + height: 60px; + object-fit: cover; +`; + +const BookTitle = styled.span` + font-size: ${typography.fontSize.sm}; + color: ${colors.white}; + font-weight: ${typography.fontWeight.regular}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-left: 8px; +`; + +const EmptyMessage = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + height: 60vh; + text-align: center; +`; + +const MainText = styled.div` + font-size: ${typography.fontSize.lg}; + color: ${colors.white}; + font-weight: ${typography.fontWeight.semibold}; + margin-bottom: 8px; +`; + +const SubText = styled.div` + font-size: ${typography.fontSize.sm}; + color: ${colors.grey[100]}; + font-weight: ${typography.fontWeight.regular}; +`; diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx index 0d6db926..489b66e4 100644 --- a/src/components/search/SearchBar.tsx +++ b/src/components/search/SearchBar.tsx @@ -1,6 +1,5 @@ import styled from '@emotion/styled'; import searchIcon from '../../assets/searchBar/search.svg'; - import deleteIcon from '../../assets/searchBar/delete.svg'; import { IconButton } from '../common/IconButton'; @@ -10,9 +9,17 @@ interface SearchBarProps { onChange?: (v: string) => void; onClick?: () => void; onSearch?: () => void; + isSearched?: boolean; } -const SearchBar = ({ placeholder, value, onChange, onClick, onSearch }: SearchBarProps) => { +const SearchBar = ({ + placeholder, + value, + onChange, + onClick, + onSearch, + isSearched, +}: SearchBarProps) => { return ( - {value && onChange?.('')} />} + {value && !isSearched && ( + onChange?.('')} /> + )} onSearch?.()} /> diff --git a/src/pages/group/Group.tsx b/src/pages/group/Group.tsx index 9c69b761..00adab86 100644 --- a/src/pages/group/Group.tsx +++ b/src/pages/group/Group.tsx @@ -129,7 +129,7 @@ const Group = () => { const closeCompletedGroupModal = () => setIsCompletedGroupModalOpen(false); const handleSearchBarClick = () => { - navigate('/groupSearch'); + navigate('/groupsearch'); }; return ( diff --git a/src/pages/groupSearch/GroupSearch.tsx b/src/pages/groupSearch/GroupSearch.tsx index c1d73dad..447ed10d 100644 --- a/src/pages/groupSearch/GroupSearch.tsx +++ b/src/pages/groupSearch/GroupSearch.tsx @@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom'; import SearchBar from '@/components/search/SearchBar'; import { useState } from 'react'; import RecentSearchTabs from '@/components/search/RecentSearchTabs'; -import SearchResult from '@/components/search/SearchResult'; +import GroupSearchResult from '@/components/search/GroupSearchResult'; const GroupSearch = () => { const navigate = useNavigate(); @@ -57,7 +57,7 @@ const GroupSearch = () => { }} /> {isSearching ? ( - + ) : ( { @@ -31,6 +33,8 @@ const Router = () => { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/src/pages/search/ApplyBook.tsx b/src/pages/search/ApplyBook.tsx new file mode 100644 index 00000000..41344c62 --- /dev/null +++ b/src/pages/search/ApplyBook.tsx @@ -0,0 +1,51 @@ +import TitleHeader from '@/components/common/TitleHeader'; +import { colors, typography } from '@/styles/global/global'; +import styled from '@emotion/styled'; +import leftArrow from '../../assets/common/leftArrow.svg'; +import { useNavigate } from 'react-router-dom'; +import { Wrapper } from '@/components/common/Wrapper'; + +const ApplyBook = () => { + const navigate = useNavigate(); + const handleBackButton = () => { + navigate(-1); + }; + return ( + + } + onLeftClick={handleBackButton} + /> + + texthip2025@gmail.com + 이메일로 책 제목, 출판사를 보내주시면 + 빠른 시일내로 책을 추가해드릴게요! + + + ); +}; + +export default ApplyBook; + +const TextWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 60vh; +`; + +const MainText = styled.p` + color: ${colors.white}; + font-size: ${typography.fontSize.lg}; + font-weight: ${typography.fontWeight.semibold}; + margin-bottom: 12px; +`; + +const SubText = styled.p` + color: ${colors.grey[100]}; + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.regular}; + margin-bottom: 6px; +`; diff --git a/src/pages/search/Search.tsx b/src/pages/search/Search.tsx new file mode 100644 index 00000000..b5f4007a --- /dev/null +++ b/src/pages/search/Search.tsx @@ -0,0 +1,188 @@ +import NavBar from '@/components/common/NavBar'; +import TitleHeader from '@/components/common/TitleHeader'; +import { BookSearchResult } from '@/components/search/BookSearchResult'; +import MostSearchedBooks from '@/components/search/MostSearchedBooks'; +import RecentSearchTabs from '@/components/search/RecentSearchTabs'; +import SearchBar from '@/components/search/SearchBar'; +import { colors, typography } from '@/styles/global/global'; +import styled from '@emotion/styled'; +import { useEffect, useState } from 'react'; +import leftArrow from '../../assets/common/leftArrow.svg'; + +export interface SearchedBook { + id: number; + title: string; + author: string; + publisher: string; + coverUrl: string; +} + +const dummySearchedBook: SearchedBook[] = [ + { + id: 1, + title: '채식주의자', + author: '한강', + publisher: '창비', + coverUrl: 'https://image.yes24.com/goods/17122707/XL', + }, + { + id: 2, + title: '채소 마스터 클래스', + author: '백지혜', + publisher: '세미콜론', + coverUrl: 'https://image.yes24.com/goods/109378551/XL', + }, + { + id: 3, + title: '채소 식탁', + author: '김경민', + publisher: '래디시', + coverUrl: 'https://image.yes24.com/goods/117194041/XL', + }, +]; +const Search = () => { + const [searchTerm, setSearchTerm] = useState(''); + const [isSearching, setIsSearching] = useState(false); + const [isSearched, setIsSearched] = useState(false); + + const [recentSearches, setRecentSearches] = useState([ + '딸기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]); + + return ( + + {isSearching ? ( + } + onLeftClick={handleBackButton} + /> + ) : ( +
검색
+ )} + + handleSearch(searchTerm.trim())} + isSearched={isSearched} + /> + + + {isSearching ? ( + <> + ( + {isSearched ? ( + + ) : ( + + )} + ) + + ) : ( + <> + + + + )} + + +
+ ); +}; + +export default Search; + +const Wrapper = styled.div` + display: flex; + position: relative; + flex-direction: column; + min-width: 320px; + max-width: 767px; + height: 100vh; + margin: 0 auto; + background: ${colors.black.main}; +`; + +const Header = styled.div` + display: flex; + width: 100%; + position: fixed; + top: 0; + left: 0; + right: 0; + max-width: 767px; + margin: 0 auto; + color: ${colors.white}; + font-size: ${typography.fontSize['2xl']}; + font-weight: ${typography.fontWeight['bold']}; + padding: 16px 20px; + background: ${colors.black.main}; + z-index: 1; +`; + +const SearchBarContainer = styled.div` + position: fixed; + top: 0; + left: 0; + right: 0; + max-width: 767px; + margin: 0 auto; + background: ${colors.black.main}; +`; + +const Content = styled.div` + margin-top: 132px; +`;