From a366a43ee50d834753f865b610ee297740c82fe4 Mon Sep 17 00:00:00 2001 From: Bori <85009583+Bori-github@users.noreply.github.com> Date: Mon, 6 May 2024 21:21:38 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B2=80=EC=83=89=20>=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#227)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ #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 로 변경 --- next.config.js | 14 ++- src/api/diaries.ts | 10 +- src/assets/icons/arrow-down.svg | 10 ++ src/assets/icons/check.svg | 3 + src/assets/icons/index.ts | 4 + src/components/common/HighlightText.tsx | 20 +++ src/components/common/index.ts | 1 + .../diary/ActivityDiariesContainer.tsx | 2 +- src/components/diary/DiariesContainer.tsx | 15 ++- src/components/diary/Diary.tsx | 50 +++++--- .../search/RecentSearchContainer.tsx | 44 +++++-- src/components/search/SearchHeader.tsx | 41 +++++-- src/components/search/SearchResultHeader.tsx | 116 ++++++++++++++++++ src/components/search/index.ts | 1 + src/constants/common/page.ts | 5 + src/hooks/services/queries/useDiaries.ts | 9 +- src/pages/search/[keyword].tsx | 115 +++++++++++++++++ src/pages/{search.tsx => search/index.tsx} | 41 ++++--- src/types/diary.ts | 1 + 19 files changed, 443 insertions(+), 59 deletions(-) create mode 100644 src/assets/icons/arrow-down.svg create mode 100644 src/assets/icons/check.svg create mode 100644 src/components/common/HighlightText.tsx create mode 100644 src/components/search/SearchResultHeader.tsx create mode 100644 src/pages/search/[keyword].tsx rename src/pages/{search.tsx => search/index.tsx} (55%) diff --git a/next.config.js b/next.config.js index 2cd6a2e4..f850f14b 100644 --- a/next.config.js +++ b/next.config.js @@ -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'], diff --git a/src/api/diaries.ts b/src/api/diaries.ts index d65b951c..fb57b924 100644 --- a/src/api/diaries.ts +++ b/src/api/diaries.ts @@ -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>(`${API_PATH.diaries.index}`, { + } = await axios.get>(API_PATH.diaries.index, { params: { skip: PAGE_SIZE * currentPageIndex, take: PAGE_SIZE, + searchKeyword, }, }); @@ -37,7 +41,7 @@ export const getDiariesByUsername = async ({ const currentPageIndex = currentPage - 1; const { data: { data }, - } = await axios.get>(`${API_PATH.diaries.index}`, { + } = await axios.get>(API_PATH.diaries.index, { params: { username, skip: PAGE_SIZE * currentPageIndex, diff --git a/src/assets/icons/arrow-down.svg b/src/assets/icons/arrow-down.svg new file mode 100644 index 00000000..e1a5aea5 --- /dev/null +++ b/src/assets/icons/arrow-down.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/check.svg b/src/assets/icons/check.svg new file mode 100644 index 00000000..ad270288 --- /dev/null +++ b/src/assets/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index 79e635a2..6d208325 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -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'; @@ -47,11 +49,13 @@ import WriteDiaryIcon from './write_diary.svg'; export { ArrowRightIcon, + ArrowDownIcon, BackIcon, BookmarkOffIcon, BookmarkOnIcon, CommentIcon, CloseIcon, + CheckIcon, CheckedOffIcon, CheckedOnIcon, DeleteIcon, diff --git a/src/components/common/HighlightText.tsx b/src/components/common/HighlightText.tsx new file mode 100644 index 00000000..4ef0492b --- /dev/null +++ b/src/components/common/HighlightText.tsx @@ -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, `${keyword}`); + + return ; +}; + +const Text = styled.span` + & mark { + color: ${({ theme }) => theme.colors.primary_00}; + background-color: transparent; + } +`; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 0b01ac18..93a7f453 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -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'; diff --git a/src/components/diary/ActivityDiariesContainer.tsx b/src/components/diary/ActivityDiariesContainer.tsx index d610bd0a..97527708 100644 --- a/src/components/diary/ActivityDiariesContainer.tsx +++ b/src/components/diary/ActivityDiariesContainer.tsx @@ -24,7 +24,7 @@ export const ActivityDiariesContainer = ({ {diariesData.map((diary) => { const { id } = diary; - return ; + return ; })} diff --git a/src/components/diary/DiariesContainer.tsx b/src/components/diary/DiariesContainer.tsx index 4ae06422..b5cd4f3f 100644 --- a/src/components/diary/DiariesContainer.tsx +++ b/src/components/diary/DiariesContainer.tsx @@ -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; @@ -22,12 +26,21 @@ export const DiariesContainer = ({ return (
{title} + + {header !== undefined && header} + {diariesData.map((data) => { const { diaries } = data; return diaries.map((diary) => { const { id } = diary; - return ; + return ( + + ); }); })} diff --git a/src/components/diary/Diary.tsx b/src/components/diary/Diary.tsx index 0b4c7e35..23b59711 100644 --- a/src/components/diary/Diary.tsx +++ b/src/components/diary/Diary.tsx @@ -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, @@ -37,8 +47,20 @@ const Diary = ({ return ( - {title} - {content} + + {isHighlightKeyword ? ( + <HighlightText text={title} keyword={highlightKeyword} /> + ) : ( + title + )} + + + {isHighlightKeyword ? ( + + ) : ( + content + )} + {imgUrl !== null && ( )} diff --git a/src/components/search/RecentSearchContainer.tsx b/src/components/search/RecentSearchContainer.tsx index e64a7db1..12972bb9 100644 --- a/src/components/search/RecentSearchContainer.tsx +++ b/src/components/search/RecentSearchContainer.tsx @@ -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[]; @@ -16,6 +20,20 @@ export const RecentSearchContainer = ({ }: RecentSearchContainerProps) => { const isEmptyRecentSearches = recentSearchKeywords.length === 0; + const router = useRouter(); + + const { setValue } = useFormContext(); + + const handleMoveSearchResult = (keyword: string) => async () => { + setValue('searchKeyword', keyword); + + await router.push(PAGE_PATH.search.keyword(keyword)); + }; + + const handleDeleteRecentSearch = (keyword: string) => () => { + onDeleteSearchKeyword(keyword); + }; + return ( @@ -32,21 +50,24 @@ export const RecentSearchContainer = ({ {recentSearchKeywords.map((recentSearchKeyword) => { return ( -
  • - + + - -
  • + + ); })}
    @@ -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; @@ -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} +`; diff --git a/src/components/search/SearchHeader.tsx b/src/components/search/SearchHeader.tsx index 0e7a8c2a..f739dbac 100644 --- a/src/components/search/SearchHeader.tsx +++ b/src/components/search/SearchHeader.tsx @@ -1,31 +1,55 @@ import styled from '@emotion/styled'; -import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; import { useFormContext } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form'; import type { SearchForm } from 'types/search'; import { DeleteIcon, SearchIcon } from 'assets/icons'; import { PAGE_PATH } from 'constants/common'; +import { Z_INDEX } from 'constants/styles'; import { SVGVerticalAlignStyle, theme } from 'styles'; interface SearchHeaderProps { - onSaveSearchKeyword: (keywordValue: string) => void; + onSaveSearchKeyword: (keyword: string) => void; } export const SearchHeader = ({ onSaveSearchKeyword }: SearchHeaderProps) => { - const { register, watch, setValue, handleSubmit } = + const router = useRouter(); + const { + query: { keyword }, + } = router; + + const { register, watch, setValue, setFocus, handleSubmit } = useFormContext(); const { searchKeyword: watchSearchKeyword } = watch(); const handleDeleteSearchKeyword = () => { setValue('searchKeyword', ''); + setFocus('searchKeyword'); + }; + + const handleCancel = () => { + if (keyword === undefined) { + void router.push(PAGE_PATH.main); + + return; + } + + void router.push(PAGE_PATH.search.index); }; - const onSubmit: SubmitHandler = (data) => { - // TODO: 검색 기능 구현 + const onSubmit: SubmitHandler = async (data) => { const { searchKeyword } = data; + onSaveSearchKeyword(searchKeyword); + + await router.push(PAGE_PATH.search.keyword(searchKeyword)); }; + useEffect(() => { + setValue('searchKeyword', keyword as string); + }, []); + return ( @@ -55,7 +79,9 @@ export const SearchHeader = ({ onSaveSearchKeyword }: SearchHeaderProps) => { - 취소 + + 취소 + ); }; @@ -69,6 +95,7 @@ const HeaderLayout = styled.header` top: 0; right: 0; left: 0; + z-index: ${Z_INDEX.header}; height: 54px; padding: 0 20px; border-bottom: 1px solid ${({ theme }) => theme.colors.gray_06}; @@ -111,6 +138,6 @@ const DeleteButton = styled.button<{ isVisible: boolean }>` ${SVGVerticalAlignStyle} `; -const CancelLink = styled(Link)` +const CancelButton = styled.button` ${({ theme }) => theme.fonts.body_05} `; diff --git a/src/components/search/SearchResultHeader.tsx b/src/components/search/SearchResultHeader.tsx new file mode 100644 index 00000000..a268333c --- /dev/null +++ b/src/components/search/SearchResultHeader.tsx @@ -0,0 +1,116 @@ +import styled from '@emotion/styled'; +import { useState } from 'react'; +import { ArrowDownIcon, CheckIcon } from 'assets/icons'; +import { Popover } from 'components/common'; +import { useClickOutside } from 'hooks/common'; + +interface SearchResultHeaderProps { + totalCount: number; +} + +interface SortOptions { + id: string; + title: string; + selected: boolean; +} + +const initialSortOptions = [ + { + id: 'latest', + title: '최신순', + selected: true, + }, + { + id: 'comments', + title: '댓글순', + selected: false, + }, +]; + +export const SearchResultHeader = ({ totalCount }: SearchResultHeaderProps) => { + const [sortOptions, setSortOptions] = + useState(initialSortOptions); + + const { ref, isVisible, setIsVisible } = useClickOutside(); + + const handleSortSearchResult = () => { + setIsVisible((state) => !state); + }; + + // TODO: 정렬 기능 추가 + const handleSelectSortOption = (selectedIndex: number) => () => { + setSortOptions((prevState) => { + return prevState.map((state, stateIndex) => ({ + ...state, + selected: stateIndex === selectedIndex, + })); + }); + }; + + return ( + + {`총 ${totalCount}건`} + + {sortOptions.find((option) => option.selected)?.title} + + + {isVisible && ( + +
      + {sortOptions.map((option, index) => { + const { id, selected, title } = option; + return ( +
    • + + {title} + {selected && } + +
    • + ); + })} +
    +
    + )} +
    + ); +}; + +const Container = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + padding: 26px 20px 6px; + color: ${({ theme }) => theme.colors.black}; + ${({ theme }) => theme.fonts.body_08} +`; + +const SortSearchResultButton = styled.button` + display: flex; + align-items: center; + gap: 6px; +`; + +const SortOptionButton = styled.button<{ selected: boolean }>` + display: flex; + align-items: center; + gap: 6px; + width: 140px; + padding: 13px 16px; + color: ${({ theme, selected }) => + selected ? theme.colors.primary_00 : theme.colors.gray_01}; + ${({ theme }) => theme.fonts.button_01} + text-align: left; +`; + +const TotalCountText = styled.span` + color: ${({ theme }) => theme.colors.gray_02}; +`; diff --git a/src/components/search/index.ts b/src/components/search/index.ts index 7f60bab3..831b1f52 100644 --- a/src/components/search/index.ts +++ b/src/components/search/index.ts @@ -1,3 +1,4 @@ export * from './SearchHeader'; export * from './NoSearchResults'; export * from './RecentSearchContainer'; +export * from './SearchResultHeader'; diff --git a/src/constants/common/page.ts b/src/constants/common/page.ts index 371cadf7..143c9b79 100644 --- a/src/constants/common/page.ts +++ b/src/constants/common/page.ts @@ -30,4 +30,9 @@ export const PAGE_PATH = { setting: { index: '/setting', }, + + search: { + index: '/search', + keyword: (keyword: string) => `/search/${keyword}`, + }, } as const; diff --git a/src/hooks/services/queries/useDiaries.ts b/src/hooks/services/queries/useDiaries.ts index 87b21985..165dfd25 100644 --- a/src/hooks/services/queries/useDiaries.ts +++ b/src/hooks/services/queries/useDiaries.ts @@ -2,12 +2,15 @@ import { useInfiniteQuery } from '@tanstack/react-query'; import * as api from 'api'; import { queryKeys } from 'constants/services'; -export const useDiaries = () => { +export const useDiaries = (searchKeyword?: string) => { const { data, isFetching, isFetchingNextPage, isError, fetchNextPage } = useInfiniteQuery({ - queryKey: [queryKeys.diaries], + queryKey: [queryKeys.diaries, searchKeyword], queryFn: async ({ pageParam = 1 }) => - await api.getDiaries({ currentPage: pageParam as number }), + await api.getDiaries({ + currentPage: pageParam as number, + searchKeyword, + }), getNextPageParam: (lastPage) => lastPage.nextPage, }); diff --git a/src/pages/search/[keyword].tsx b/src/pages/search/[keyword].tsx new file mode 100644 index 00000000..e1582e55 --- /dev/null +++ b/src/pages/search/[keyword].tsx @@ -0,0 +1,115 @@ +import styled from '@emotion/styled'; +import { getServerSession } from 'next-auth'; +import { FormProvider, useForm } from 'react-hook-form'; +import type { + GetServerSideProps, + InferGetServerSidePropsType, + NextPage, +} from 'next'; +import type { SearchForm } from 'types/search'; +import { FullPageLoading, ObserverTarget, Seo } from 'components/common'; +import { DiariesContainer } from 'components/diary'; +import { + NoSearchResults, + RecentSearchContainer, + SearchHeader, + SearchResultHeader, +} from 'components/search'; +import { PAGE_PATH } from 'constants/common'; +import { useIntersectionObserver, useSearchKeywordStorage } from 'hooks/common'; +import { useDiaries } from 'hooks/services'; +import { authOptions } from 'pages/api/auth/[...nextauth]'; + +const SearchResultPage: NextPage< + InferGetServerSidePropsType +> = ({ keyword }) => { + const methods = useForm({ mode: 'onChange' }); + const { watch } = methods; + const { searchKeyword } = watch(); + + const { + keywords, + handleSaveSearchKeyword, + handleDeleteSearchKeyword, + handleDeleteAllSearchKeyword, + } = useSearchKeywordStorage(); + + const { diariesData, isLoading, isError, fetchNextPage } = + useDiaries(keyword); + const { setTargetRef } = useIntersectionObserver({ + onIntersect: fetchNextPage, + }); + + const isShowRecentSearchResult = + searchKeyword === undefined || searchKeyword.length === 0; + + if (diariesData === undefined) return ; + + return ( + <> + +
    + + + {isShowRecentSearchResult ? ( + + ) : ( + <> + } + header={ + + } + highlightKeyword={keyword} + /> + + + )} + +
    + + ); +}; +export const getServerSideProps = (async (context) => { + const { req, res, params } = context; + const keyword = params?.keyword as string; + + const session = await getServerSession(req, res, authOptions); + + if (session === null) { + return { + redirect: { + destination: PAGE_PATH.account.login, + permanent: false, + }, + }; + } + + return { props: { keyword } }; +}) satisfies GetServerSideProps; + +export default SearchResultPage; + +const Section = styled.section` + overflow-y: auto; + height: 100vh; + scrollbar-width: none; + + -ms-overflow-style: none; + + &::-webkit-scrollbar { + display: none; + } +`; diff --git a/src/pages/search.tsx b/src/pages/search/index.tsx similarity index 55% rename from src/pages/search.tsx rename to src/pages/search/index.tsx index a4698109..cf207aac 100644 --- a/src/pages/search.tsx +++ b/src/pages/search/index.tsx @@ -1,15 +1,16 @@ import styled from '@emotion/styled'; +import { getServerSession } from 'next-auth'; import { FormProvider, useForm } from 'react-hook-form'; -import type { NextPage } from 'next'; +import type { GetServerSideProps, NextPage } from 'next'; import type { SearchForm } from 'types/search'; import { Seo } from 'components/common'; import { RecentSearchContainer, SearchHeader } from 'components/search'; +import { PAGE_PATH } from 'constants/common'; import { useSearchKeywordStorage } from 'hooks/common'; +import { authOptions } from 'pages/api/auth/[...nextauth]'; const SearchPage: NextPage = () => { const methods = useForm({ mode: 'onChange' }); - const { watch } = methods; - const { searchKeyword } = watch(); const { keywords, @@ -18,34 +19,44 @@ const SearchPage: NextPage = () => { handleDeleteAllSearchKeyword, } = useSearchKeywordStorage(); - const isShowRecentSearchResult = - searchKeyword === undefined || searchKeyword.length === 0; - return ( <>
    - {isShowRecentSearchResult ? ( - - ) : ( -
    검색결과
    - )} +
    ); }; +export const getServerSideProps: GetServerSideProps = async (context) => { + const { req, res } = context; + const session = await getServerSession(req, res, authOptions); + + if (session === null) { + return { + redirect: { + destination: PAGE_PATH.account.login, + permanent: false, + }, + }; + } + + return { props: { session } }; +}; + export default SearchPage; const Section = styled.section` overflow-y: auto; + height: 100vh; scrollbar-width: none; -ms-overflow-style: none; diff --git a/src/types/diary.ts b/src/types/diary.ts index 40bd928a..f7bd09c5 100644 --- a/src/types/diary.ts +++ b/src/types/diary.ts @@ -32,6 +32,7 @@ export type DiaryForm = Pick< export interface GetDiariesRequest { currentPage: number; + searchKeyword?: string; } export interface GetDiariesByUsernameRequest extends GetDiariesRequest {