Skip to content

Commit

Permalink
[FE] Feat/#554 search 지도 검색 기능 구현 (#555)
Browse files Browse the repository at this point in the history
* feat: SearchBar 컴포넌트 구현

* feat: Search 페이지 구현

* feat: 홈페이지에 searchBar 적용

* refactor: 불필요한 memo 제거
  • Loading branch information
jiwonh423 authored Oct 6, 2023
1 parent 0c0d6b5 commit 4c55e87
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 2 deletions.
2 changes: 2 additions & 0 deletions frontend/src/assets/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions frontend/src/components/SearchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useState } from 'react';
import styled from 'styled-components';
import useNavigator from '../../hooks/useNavigator';
import SearchIcon from '../../assets/search.svg';

const SearchBar = () => {
const { routingHandlers } = useNavigator();

const [searchTerm, setSearchTerm] = useState('');

const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value);
};

const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
routingHandlers.search(searchTerm);
};

return (
<SearchBarWrapper onSubmit={onSubmit}>
<StyledSearchIcon />
<SearchInput
type="text"
placeholder="관심있는 키워드를 입력하세요"
onChange={onInputChange}
/>
</SearchBarWrapper>
);
};
export default SearchBar;

const SearchBarWrapper = styled.form`
display: flex;
padding-left: 20px;
position: relative;
border-radius: 5px;
border: 1px solid #ccc;
box-shadow: 0px 1px 5px 3px rgba(0, 0, 0, 0.12);
`;

const SearchInput = styled.input`
height: 45px;
width: 100%;
outline: none;
border: none;
border-radius: 5px;
padding: 0 60px 0 20px;
font-size: 18px;
font-weight: 500;
&:focus {
outline: none !important;
box-shadow:
inset -1px -1px rgba(0, 0, 0, 0.075),
inset -1px -1px rgba(0, 0, 0, 0.075),
inset -3px -3px rgba(255, 255, 255, 0.6),
inset -4px -4px rgba(255, 255, 255, 0.6),
inset rgba(255, 255, 255, 0.6),
inset rgba(64, 64, 64, 0.15);
}
`;

const StyledSearchIcon = styled(SearchIcon)`
position: absolute;
top: 50%;
left: 10px;
transform: translateY(-50%);
width: 20px;
height: 20px;
fill: #ccc;
`;
1 change: 1 addition & 0 deletions frontend/src/hooks/useNavigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const useNavigator = () => {
routePage('/new-pin', topicId);
closeModal('addMapOrPin');
},
search: (searchTerm: string) => routePage(`/search?${searchTerm}`),
goToPopularTopics: () => routePage('see-all/popularity'),
goToNearByMeTopics: () => routePage('see-all/near'),
goToLatestTopics: () => routePage('see-all/latest'),
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Space from '../components/common/Space';
import useNavigator from '../hooks/useNavigator';
import { css, styled } from 'styled-components';
import { styled } from 'styled-components';
import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
import { FULLSCREEN } from '../constants';
import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
import { Suspense, lazy, useContext, useEffect } from 'react';
import { MarkerContext } from '../context/MarkerContext';
import TopicCardContainerSkeleton from '../components/Skeletons/TopicListSkeleton';
import { setFullScreenResponsive } from '../constants/responsive';
import SearchBar from '../components/SearchBar/SearchBar';

const TopicListContainer = lazy(
() => import('../components/TopicCardContainer'),
Expand All @@ -33,7 +34,9 @@ const Home = () => {

return (
<Wrapper>
<Space size={5} />
<Space size={1} />
<SearchBar />
<Space size={1} />
<Suspense fallback={<TopicCardContainerSkeleton />}>
<TopicListContainer
url="/topics/bests"
Expand Down
138 changes: 138 additions & 0 deletions frontend/src/pages/Search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import styled from 'styled-components';
import { setFullScreenResponsive } from '../constants/responsive';
import Space from '../components/common/Space';
import SearchBar from '../components/SearchBar/SearchBar';
import { Fragment, useEffect, useState } from 'react';
import Flex from '../components/common/Flex';
import Box from '../components/common/Box';
import Text from '../components/common/Text';
import TopicCard from '../components/TopicCard';
import { TopicCardProps } from '../types/Topic';
import { useLocation } from 'react-router-dom';
import useGet from '../apiHooks/useGet';

const Search = () => {
const { fetchGet } = useGet();

const [originalTopics, setOriginalTopics] = useState<TopicCardProps[] | null>(
null,
);
const [displayedTopics, setDisplayedTopics] = useState<
TopicCardProps[] | null
>(null);
const searchQuery = decodeURIComponent(useLocation().search.substring(1));

const getTopicsFromServer = async () => {
fetchGet<TopicCardProps[]>(
'/topics',
'지도를 가져오는데 실패했습니다.',
(response) => {
setOriginalTopics(response);
const searchResult = response.filter((topic) =>
topic.name.includes(searchQuery),
);
setDisplayedTopics(searchResult);
},
);
};

useEffect(() => {
getTopicsFromServer();
}, []);

useEffect(() => {
if (originalTopics) {
const searchResult = originalTopics.filter((topic) =>
topic.name.includes(searchQuery),
);
setDisplayedTopics(searchResult);
}
}, [searchQuery]);

return (
<Wrapper>
<Space size={1} />
<SearchBar />
<Space size={1} />
<Flex $justifyContent="space-between" $alignItems="flex-end">
<Box>
<Text
color="black"
$fontSize="extraLarge"
$fontWeight="bold"
tabIndex={0}
>
찾았을 지도?
</Text>
<Space size={0} />
<Text
color="gray"
$fontSize="default"
$fontWeight="normal"
tabIndex={1}
>
검색한 지도를 확인해보세요.
</Text>
</Box>
</Flex>
<Space size={6} />

{displayedTopics?.length === 0 ? (
// 검색 결과가 없을 때의 UI
<EmptyWrapper>
<Flex $alignItems="center">
<Space size={1} />
<Text color="black" $fontSize="default" $fontWeight="normal">
'{searchQuery}'에 대한
{'검색 결과가 없습니다.'}
</Text>
<Space size={4} />
</Flex>
<Space size={5} />
</EmptyWrapper>
) : (
<CardListWrapper>
{displayedTopics?.map((topic) => (
<Fragment key={topic.id}>
<TopicCard
cardType="default"
id={topic.id}
image={topic.image}
name={topic.name}
creator={topic.creator}
updatedAt={topic.updatedAt}
pinCount={topic.pinCount}
bookmarkCount={topic.bookmarkCount}
isInAtlas={topic.isInAtlas}
isBookmarked={topic.isBookmarked}
/>
</Fragment>
))}
</CardListWrapper>
)}
</Wrapper>
);
};

export default Search;

const Wrapper = styled.article`
width: 1036px;
margin: 0 auto;
position: relative;
${setFullScreenResponsive()}
`;

const CardListWrapper = styled.ul`
display: flex;
flex-wrap: wrap;
gap: 20px;
`;

const EmptyWrapper = styled.section`
height: 240px;
display: flex;
flex-direction: column;
align-items: center;
`;
6 changes: 6 additions & 0 deletions frontend/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import RootPage from './pages/RootPage';
import { ReactNode } from 'react';
import AuthLayout from './components/Layout/AuthLayout';
import NotFound from './pages/NotFound';
import Search from './pages/Search';

const SelectedTopic = lazy(() => import('./pages/SelectedTopic'));
const NewPin = lazy(() => import('./pages/NewPin'));
Expand Down Expand Up @@ -147,6 +148,11 @@ const routes: routeElement[] = [
),
withAuth: false,
},
{
path: '/search',
element: <Search />,
withAuth: false,
},
],
},
];
Expand Down

0 comments on commit 4c55e87

Please sign in to comment.