Skip to content

Commit

Permalink
검색 > 검색 결과 기능 구현 (#227)
Browse files Browse the repository at this point in the history
* ✨ #226 - getDiaries API props에 searchKeyword 추가

* ✨ #226 - PAGE_PATH 에 검색 관련 path 추가

* 🚚 #226 - search page 폴더 구조 변경

- 불필요한 코드 제거
- 로그인 상태가 아닌 경우, 로그인 페이지로 라우팅 처리

* ✨ #226 - 검색창에 검색어 입력 시 검색 결과 페이지로 이동

- 검색 페이지/검색 결과 페이지에 따라 취소 버튼 클릭 시 라우팅 처리
- 검색 결과 페이지 새로고침 시 검색 결과 나타나도록 적용

* ✨ #226 - 검색 결과 페이지 구현

* ✨ #226 - 검색 결과 총 갯수 노출

* 🔍 #226 - seo title 수정

* 🔧 #226 - 뒤로가기 시 스크롤 복원을 위한 설정 추가

* 🍱 #226 - 아이콘 추가

* ✨ #226 - 검색 결과 정렬을 위한 UI 구현

* ✨ #226 - 최근 검색어 클릭 시 해당 검색 결과 페이지로 이동

* ✨ #226 - 검색어와 일치하는 텍스트 하이라이트 UI 적용

* 💚 #226 - 누락된 Diary 컴포넌트 props 변경으로 인한 빌드 에러 해결

* 🔥 #226 - 불필요한 템플릿 리터럴 제거

* ♻️ #226 - setSortOptions 타입 지정 및 리팩토링

* 🏷️ #226 - getServerSideProps 타입 수정 및 불필요한 타입 단언 제거

* 🩹 #226 - 잘못 적용된 searchKeyword > keyword 로 변경
  • Loading branch information
Bori-github authored May 6, 2024
1 parent 97efab8 commit a366a43
Show file tree
Hide file tree
Showing 19 changed files with 443 additions and 59 deletions.
14 changes: 8 additions & 6 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: { // NOTE: images domains 설정이 최상위에 있어야 합니다.
images: {
// NOTE: images domains 설정이 최상위에 있어야 합니다.
domains: [
"add.bucket.s3.amazonaws.com",
"dummyimage.com", // 목데이터의 이미지 URL 도메인, 추후 삭제
"robohash.org" // 목데이터의 이미지 URL 도메인, 추후 삭제
'add.bucket.s3.amazonaws.com',
'dummyimage.com', // 목데이터의 이미지 URL 도메인, 추후 삭제
'robohash.org', // 목데이터의 이미지 URL 도메인, 추후 삭제
],
formats: ['image/avif', 'image/webp'],
},
compiler: {
emotion: true
emotion: true,
},
experimental: {
fontLoaders: [
{ loader: '@next/font/google', options: { subsets: ['latin'] } },
],
scrollRestoration: true,
},
reactStrictMode: true,
webpack: config => {
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
Expand Down
10 changes: 7 additions & 3 deletions src/api/diaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ import type { OnlyMessageResponse, SuccessResponse } from 'types/response';
import { API_PATH, PAGE_SIZE } from 'constants/services';
import axios from 'lib/axios';

export const getDiaries = async ({ currentPage }: GetDiariesRequest) => {
export const getDiaries = async ({
currentPage,
searchKeyword,
}: GetDiariesRequest) => {
const currentPageIndex = currentPage - 1;
const {
data: { data },
} = await axios.get<SuccessResponse<Diaries>>(`${API_PATH.diaries.index}`, {
} = await axios.get<SuccessResponse<Diaries>>(API_PATH.diaries.index, {
params: {
skip: PAGE_SIZE * currentPageIndex,
take: PAGE_SIZE,
searchKeyword,
},
});

Expand All @@ -37,7 +41,7 @@ export const getDiariesByUsername = async ({
const currentPageIndex = currentPage - 1;
const {
data: { data },
} = await axios.get<SuccessResponse<Diaries>>(`${API_PATH.diaries.index}`, {
} = await axios.get<SuccessResponse<Diaries>>(API_PATH.diaries.index, {
params: {
username,
skip: PAGE_SIZE * currentPageIndex,
Expand Down
10 changes: 10 additions & 0 deletions src/assets/icons/arrow-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import ArrowDownIcon from './arrow-down.svg';
import ArrowRightIcon from './arrow-right-circle.svg';
import BackIcon from './back.svg';
import BadIcon from './bad.svg';
import BlockIcon from './block.svg';
import BookmarkOffIcon from './bookmark_off.svg';
import BookmarkOnIcon from './bookmark_on.svg';
import CheckIcon from './check.svg';
import CheckedOffIcon from './checkbox_off.svg';
import CheckedOnIcon from './checkbox_on.svg';
import CloseIcon from './close.svg';
Expand Down Expand Up @@ -47,11 +49,13 @@ import WriteDiaryIcon from './write_diary.svg';

export {
ArrowRightIcon,
ArrowDownIcon,
BackIcon,
BookmarkOffIcon,
BookmarkOnIcon,
CommentIcon,
CloseIcon,
CheckIcon,
CheckedOffIcon,
CheckedOnIcon,
DeleteIcon,
Expand Down
20 changes: 20 additions & 0 deletions src/components/common/HighlightText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import styled from '@emotion/styled';

interface HighlightTextProps {
text: string;
keyword: string;
}

export const HighlightText = ({ text, keyword }: HighlightTextProps) => {
const regExp = new RegExp(keyword, 'gi');
const replaceText = text.replace(regExp, `<mark>${keyword}</mark>`);

return <Text dangerouslySetInnerHTML={{ __html: replaceText }} />;
};

const Text = styled.span`
& mark {
color: ${({ theme }) => theme.colors.primary_00};
background-color: transparent;
}
`;
1 change: 1 addition & 0 deletions src/components/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './ConfirmModal';
export * from './IconButton';
export * from './FloatingMenu';
export * from './FloatingMenuButton';
export * from './HighlightText';
export * from './ResponsiveImage';
export * from './Seo';
export * from './Tab';
Expand Down
2 changes: 1 addition & 1 deletion src/components/diary/ActivityDiariesContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const ActivityDiariesContainer = ({
<List>
{diariesData.map((diary) => {
const { id } = diary;
return <Diary key={`diary-list-${id}`} {...diary} />;
return <Diary key={`diary-list-${id}`} diaryData={diary} />;
})}
</List>
</section>
Expand Down
15 changes: 14 additions & 1 deletion src/components/diary/DiariesContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ interface DiariesContainerProps {
title: string;
diariesData: Diaries[];
empty: JSX.Element;
header?: JSX.Element;
highlightKeyword?: string;
}

export const DiariesContainer = ({
title,
diariesData,
empty,
header,
highlightKeyword,
}: DiariesContainerProps) => {
const { totalCount } = diariesData[0];
const isEmptyDiaries = totalCount === 0;
Expand All @@ -22,12 +26,21 @@ export const DiariesContainer = ({
return (
<section>
<Title>{title}</Title>

{header !== undefined && header}

<List>
{diariesData.map((data) => {
const { diaries } = data;
return diaries.map((diary) => {
const { id } = diary;
return <Diary key={`diary-list-${id}`} {...diary} />;
return (
<Diary
key={`diary-list-${id}`}
diaryData={diary}
highlightKeyword={highlightKeyword}
/>
);
});
})}
</List>
Expand Down
50 changes: 36 additions & 14 deletions src/components/diary/Diary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@ import {
HeartOnIcon,
HeartOffIcon,
} from 'assets/icons';
import { ResponsiveImage } from 'components/common';
import { HighlightText, ResponsiveImage } from 'components/common';
import { PAGE_PATH } from 'constants/common';
import { useHandleFavorite, useHandleBookmark } from 'hooks/services/common';
import { EllipsisStyle } from 'styles';
import { dateFormat, timeFormat } from 'utils';

interface DiaryProps {
diaryData: DiaryDetail;
highlightKeyword?: string;
}

const Diary = ({
id,
title,
content,
imgUrl,
commentCount,
favoriteCount,
isFavorite,
isBookmark,
createdAt,
author,
}: DiaryDetail) => {
diaryData: {
id,
title,
content,
imgUrl,
commentCount,
favoriteCount,
isFavorite,
isBookmark,
createdAt,
author,
},
highlightKeyword,
}: DiaryProps) => {
const isHighlightKeyword = highlightKeyword !== undefined;

const handleFavorite = useHandleFavorite({ isFavorite, id });
const handleBookmark = useHandleBookmark({
isBookmark,
Expand All @@ -37,8 +47,20 @@ const Diary = ({
return (
<Container>
<ContentContainer>
<Title>{title}</Title>
<ContentLink href={PAGE_PATH.diary.detail(id)}>{content}</ContentLink>
<Title>
{isHighlightKeyword ? (
<HighlightText text={title} keyword={highlightKeyword} />
) : (
title
)}
</Title>
<ContentLink href={PAGE_PATH.diary.detail(id)}>
{isHighlightKeyword ? (
<HighlightText text={content} keyword={highlightKeyword} />
) : (
content
)}
</ContentLink>
{imgUrl !== null && (
<ResponsiveImage src={imgUrl} alt={title} aspectRatio={2 / 1} />
)}
Expand Down
44 changes: 35 additions & 9 deletions src/components/search/RecentSearchContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import styled from '@emotion/styled';
import { useRouter } from 'next/router';
import { useFormContext } from 'react-hook-form';
import { NoSearchResults } from './NoSearchResults';
import type { SearchForm } from 'types/search';
import { CloseIcon } from 'assets/icons';
import { theme } from 'styles';
import { PAGE_PATH } from 'constants/common';
import { SVGVerticalAlignStyle, theme } from 'styles';

interface RecentSearchContainerProps {
recentSearchKeywords: string[];
Expand All @@ -16,6 +20,20 @@ export const RecentSearchContainer = ({
}: RecentSearchContainerProps) => {
const isEmptyRecentSearches = recentSearchKeywords.length === 0;

const router = useRouter();

const { setValue } = useFormContext<SearchForm>();

const handleMoveSearchResult = (keyword: string) => async () => {
setValue('searchKeyword', keyword);

await router.push(PAGE_PATH.search.keyword(keyword));
};

const handleDeleteRecentSearch = (keyword: string) => () => {
onDeleteSearchKeyword(keyword);
};

return (
<Container>
<TitleContainer>
Expand All @@ -32,21 +50,24 @@ export const RecentSearchContainer = ({
<RecentSearchList>
{recentSearchKeywords.map((recentSearchKeyword) => {
return (
<li key={recentSearchKeyword}>
<RecentSearchButton
<RecentSearchItem key={recentSearchKeyword}>
<button
type="button"
onClick={() => {
onDeleteSearchKeyword(recentSearchKeyword);
}}
onClick={handleMoveSearchResult(recentSearchKeyword)}
>
{recentSearchKeyword}
</button>
<DeleteButton
type="button"
onClick={handleDeleteRecentSearch(recentSearchKeyword)}
>
<CloseIcon
width={16}
height={16}
stroke={theme.colors.gray_04}
/>
</RecentSearchButton>
</li>
</DeleteButton>
</RecentSearchItem>
);
})}
</RecentSearchList>
Expand Down Expand Up @@ -77,11 +98,12 @@ const DeleteAllButton = styled.button`
const RecentSearchList = styled.ul`
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
padding-top: 18px;
`;

const RecentSearchButton = styled.button`
const RecentSearchItem = styled.li`
display: flex;
align-items: center;
gap: 12px;
Expand All @@ -90,3 +112,7 @@ const RecentSearchButton = styled.button`
background-color: ${({ theme }) => theme.colors.bg_01};
${({ theme }) => theme.fonts.body_05}
`;

const DeleteButton = styled.button`
${SVGVerticalAlignStyle}
`;
Loading

0 comments on commit a366a43

Please sign in to comment.