diff --git a/src/components/common/PostSection/index.tsx b/src/components/common/PostSection/index.tsx
new file mode 100644
index 00000000..d4ff6c6a
--- /dev/null
+++ b/src/components/common/PostSection/index.tsx
@@ -0,0 +1,46 @@
+import { Styled } from './styled'
+import type { PostSectionProps } from './types'
+import { useGetCategoriesQuery } from '@apis/post'
+import { SearchOptions, CategorySlideFilter, ProductList } from '@components'
+
+export const PostSection = ({
+ infinitePosts,
+ postsCount = 0,
+ searchOptions,
+ onChangeSearchOption
+}: PostSectionProps) => {
+ const getCategoriesQuery = useGetCategoriesQuery()
+ const categories =
+ getCategoriesQuery.data?.map(({ code, name }) => ({
+ code,
+ name
+ })) || []
+
+ return (
+ <>
+
+ onChangeSearchOption('category', code)}
+ />
+
+
+ {postsCount > 0 ? (
+
+ ) : (
+
+ 검색 결과 없음
+
+ 찾으시는 검색 결과가 없어요
+
+
+ )}
+ >
+ )
+}
diff --git a/src/components/common/PostSection/styled.ts b/src/components/common/PostSection/styled.ts
new file mode 100644
index 00000000..34cff31e
--- /dev/null
+++ b/src/components/common/PostSection/styled.ts
@@ -0,0 +1,38 @@
+import { css } from '@emotion/react'
+import styled from '@emotion/styled'
+
+const CategorySliderWrapper = styled.div`
+ /* TODO: useMedia를 사용한 조건부 렌더링시 hydration 에러가 발생해 스타일로 우선 적용 했습니다. */
+ ${({ theme }) => theme.mediaQuery.tablet} {
+ display: none;
+ }
+`
+
+const Placeholder = styled.div`
+ width: 100%;
+ height: 100%;
+ margin: 120px 0;
+
+ text-align: center;
+`
+
+const PlaceholderTitle = styled.p`
+ margin-bottom: 8px;
+
+ ${({ theme }) => theme.fonts.subtitle01B}
+`
+
+const PlaceholderDescription = styled.p`
+ ${({ theme }) => css`
+ color: ${theme.colors.grayScale70};
+
+ ${theme.fonts.body01M};
+ `}
+`
+
+export const Styled = {
+ CategorySliderWrapper,
+ Placeholder,
+ PlaceholderTitle,
+ PlaceholderDescription
+}
diff --git a/src/components/common/PostSection/types.ts b/src/components/common/PostSection/types.ts
new file mode 100644
index 00000000..2d0a6df9
--- /dev/null
+++ b/src/components/common/PostSection/types.ts
@@ -0,0 +1,12 @@
+import type {
+ SearchOptionsState,
+ OnChangeSearchOptions
+} from '../../result/SearchOptions/types'
+import type { ProductListProps } from '@components/home'
+
+export type PostSectionProps = {
+ postsCount?: number
+ infinitePosts: ProductListProps
+ searchOptions: SearchOptionsState
+ onChangeSearchOption: OnChangeSearchOptions
+}
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index 34c9d8bb..8111c6fb 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -3,3 +3,4 @@ export * from './CommonModal'
export * from './Header'
export * from './AlertModal'
export * from './Dialog'
+export * from './PostSection'
diff --git a/src/components/home/CategorySlider/index.tsx b/src/components/home/CategorySlider/index.tsx
index 917bdb89..9baa8263 100644
--- a/src/components/home/CategorySlider/index.tsx
+++ b/src/components/home/CategorySlider/index.tsx
@@ -4,6 +4,8 @@ import { useState, useEffect, useRef, useCallback } from 'react'
import type { ReactElement, TouchEventHandler } from 'react'
import { Styled } from './styled'
import { useGetCategoriesQuery } from '@apis/post'
+import { SORT_OPTIONS } from '@constants/app'
+import { toQueryString } from '@utils/format'
const CategorySlider = (): ReactElement => {
const containerRef = useRef(null)
@@ -84,7 +86,7 @@ const CategorySlider = (): ReactElement => {
onTouchMove={isDrag ? onDragMove : undefined}
onTouchStart={onDragStart}>
{isDesktop && (
-
+ <>
{isFirstCategory ? (
) : (
@@ -105,14 +107,17 @@ const CategorySlider = (): ReactElement => {
onClick={handleRightArrowClick}
/>
)}
-
+ >
)}
{getCategoryQuery.data?.map(({ code, name, imageUrl }) => {
return (
-
+
theme.fonts.headline02B}
@@ -81,43 +82,46 @@ export const CateGoryBox = styled.div`
}
`
-export const ArrowBox = styled.div`
+const arrowStyle = css`
position: absolute;
- z-index: 999;
- display: flex;
- align-self: center;
- justify-content: space-between;
-
- width: 100%;
- margin-bottom: 30px;
-`
+ top: 32px;
+ z-index: ${theme.zIndex.common};
-export const LeftArrow = styled(IconButton)`
width: 24px;
height: 24px;
border-radius: 100%;
- background-color: ${({ theme }): string => theme.colors.white};
+ background-color: ${theme.colors.white};
filter: drop-shadow(0 2px 6px rgb(0 0 0 / 25%));
`
-export const RightArrow = styled(IconButton)`
- width: 24px;
- height: 24px;
- border-radius: 100%;
+export const LeftArrow = styled(IconButton)`
+ left: 0;
+
+ ${arrowStyle}
+`
- background-color: ${({ theme }): string => theme.colors.white};
+export const RightArrow = styled(IconButton)`
+ right: 0;
- filter: drop-shadow(0 2px 6px rgb(0 0 0 / 25%));
+ ${arrowStyle}
transform: scaleX(-1);
`
-export const CategoryItem = styled.div`
+export const CategoryItem = styled.button`
+ display: flex;
+ justify-content: center;
+
width: 100%;
max-width: 108px;
height: 118px;
+ border: none;
+
+ background: transparent;
+
+ cursor: pointer;
transition: 0.5s;
@@ -142,6 +146,7 @@ export const CategoryImgWrapper = styled.div`
width: 108px;
height: 86px;
+ cursor: pointer;
${({ theme }) => css`
border-radius: ${theme.radius.round12};
@@ -192,7 +197,6 @@ export const Styled = {
CateGoryBoxWrapper,
CateGoryBox,
CategoryLink,
- ArrowBox,
LeftArrow,
RightArrow,
CategoryItem,
diff --git a/src/components/home/ProductList/types.ts b/src/components/home/ProductList/types.ts
index ccd76216..40f23696 100644
--- a/src/components/home/ProductList/types.ts
+++ b/src/components/home/ProductList/types.ts
@@ -3,14 +3,10 @@ import type {
InfiniteData,
InfiniteQueryObserverResult
} from '@tanstack/react-query'
-import type { GetPostsReq, GetPostsRes } from '@apis/post'
+import type { GetPostsRes } from '@apis/post'
export type ProductListProps = {
postData?: GetPostsRes[]
- filterOption?: Pick<
- GetPostsReq,
- 'sort' | 'category' | 'minPrice' | 'maxPrice'
- >
hasNextPage?: boolean
fetchNextPage?(
options?: FetchNextPageOptions
diff --git a/src/components/result/CategoryHeader/index.tsx b/src/components/result/CategoryHeader/index.tsx
index 2d697ae4..65c6716b 100644
--- a/src/components/result/CategoryHeader/index.tsx
+++ b/src/components/result/CategoryHeader/index.tsx
@@ -3,14 +3,12 @@ import { Styled } from './styled'
import type { ResultHeaderProps } from './types'
const ResultHeader = ({
- searchResult,
+ resultMessage,
postsCount
}: ResultHeaderProps): ReactElement => {
return (
-
- "{searchResult}"의 검색결과
-
+ {resultMessage}
{postsCount}개
diff --git a/src/components/result/CategoryHeader/types.ts b/src/components/result/CategoryHeader/types.ts
index 3ab37494..6f601d53 100644
--- a/src/components/result/CategoryHeader/types.ts
+++ b/src/components/result/CategoryHeader/types.ts
@@ -1,4 +1,4 @@
export type ResultHeaderProps = {
- searchResult: string
+ resultMessage: string
postsCount: number
}
diff --git a/src/components/result/SearchOptions/types.ts b/src/components/result/SearchOptions/types.ts
index 9a25f870..9bd9f4c4 100644
--- a/src/components/result/SearchOptions/types.ts
+++ b/src/components/result/SearchOptions/types.ts
@@ -9,14 +9,15 @@ export type SearchOptionsState = {
max?: number
}
}
+
export type OnChangeSearchOptions = (
name: KeyOf,
value: ValueOf
) => void
export type SearchOptionsProps = {
- categories: Pick[]
postsCount?: number
+ categories: Pick[]
searchOptions: SearchOptionsState
onChangeSearchOption: OnChangeSearchOptions
}
diff --git a/src/pages/categories/[categoryCode].tsx b/src/pages/categories/[categoryCode].tsx
new file mode 100644
index 00000000..65eeb7f0
--- /dev/null
+++ b/src/pages/categories/[categoryCode].tsx
@@ -0,0 +1,138 @@
+import styled from '@emotion/styled'
+import type { GetServerSideProps, NextPage } from 'next'
+import { useRouter } from 'next/router'
+import { useState } from 'react'
+import type {
+ SearchOptionsState,
+ OnChangeSearchOptions
+} from '@components/result/SearchOptions/types'
+import { useGetCategoriesQuery, useGetInfinitePostsQuery } from '@apis'
+import { PostSection, ResultHeader } from '@components'
+import type { SortOptionCodes, TradeTypeCodes } from '@types'
+import { find, removeNullish, toQueryString } from '@utils'
+
+const DEFAULT_PER_PAGE = 8
+// TODO: 포스트 전체 갯수 내려달라고 요청해놓았습니다
+const POSTS_COUNT_MOCK = 10
+
+type CategoriesProps = {
+ category?: string
+ sort?: SortOptionCodes
+ minPrice?: number
+ maxPrice?: number
+ tradeType?: TradeTypeCodes
+}
+
+export const getServerSideProps: GetServerSideProps = async ({
+ query
+}) => ({
+ props: {
+ category: query.categoryCode as string,
+ sort: (query.sort as SortOptionCodes) || 'CREATED_DATE_DESC',
+ minPrice: Number(query.min_price),
+ maxPrice: Number(query.max_price),
+ tradeType: (query.tradeType as TradeTypeCodes) || null
+ }
+})
+
+const Categories: NextPage = ({
+ category,
+ sort,
+ minPrice,
+ maxPrice,
+ tradeType
+}: CategoriesProps) => {
+ const [searchOptions, setSearchOptions] = useState({
+ category,
+ sort: 'CREATED_DATE_DESC',
+ priceRange: {}
+ })
+ const router = useRouter()
+
+ const getCategoriesQuery = useGetCategoriesQuery()
+
+ const searchParams = removeNullish({
+ minPrice: searchOptions.priceRange?.min ?? minPrice,
+ maxPrice: searchOptions.priceRange?.max ?? maxPrice,
+ tradeType: searchOptions.tradeType ?? tradeType,
+ sort: searchOptions.sort ?? sort
+ })
+
+ const categories =
+ getCategoriesQuery.data?.map(({ code, name }) => ({ code, name })) || []
+ const currentCategory = find(categories, { code: searchOptions.category })
+
+ const infinitePosts = useGetInfinitePostsQuery({
+ lastId: null,
+ limit: DEFAULT_PER_PAGE,
+ ...searchParams
+ })
+
+ const searchByCategory = ({
+ category,
+ priceRange,
+ ...params
+ }: SearchOptionsState) => {
+ router.push(
+ `/categories/${category}?${toQueryString({
+ ...params,
+ minPrice: priceRange.min,
+ maxPrice: priceRange.max
+ })}`
+ )
+ }
+
+ const handleChangeSearchOptions: OnChangeSearchOptions = (name, value) => {
+ const newSearchOptions = {
+ ...searchOptions,
+ [name]: value
+ }
+
+ setSearchOptions(newSearchOptions)
+ searchByCategory(newSearchOptions)
+ }
+
+ return (
+
+
+
+
+
+
+ )
+}
+
+const ResultWrapper = styled.div`
+ width: 100%;
+ max-width: 1200px;
+ ${({ theme }): string => theme.mediaQuery.tablet} {
+ padding-right: 24px;
+ padding-left: 24px;
+ }
+ ${({ theme }): string => theme.mediaQuery.mobile} {
+ padding-right: 16px;
+ padding-left: 16px;
+ }
+`
+
+const Layout = styled.div`
+ display: flex;
+ justify-content: center;
+
+ width: 100%;
+ margin-top: 68px;
+`
+
+export default Categories
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index b93e4b8d..3144c422 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -7,6 +7,10 @@ import { ProductList } from '../components/home/ProductList'
import { useGetInfinitePostsQuery } from '@apis/post'
import { CategorySlider, HomeBanner } from '@components'
+const DEFAULT_PER_PAGE = 8
+// TODO: 포스트 전체 갯수 내려달라고 요청해놓았습니다
+const POSTS_COUNTS_MOCK = 10
+
const Home: NextPage = () => {
const {
data: postList,
@@ -14,20 +18,17 @@ const Home: NextPage = () => {
hasNextPage
} = useGetInfinitePostsQuery({
lastId: null,
- limit: 8
+ limit: DEFAULT_PER_PAGE
})
const router = useRouter()
- // TODO: 포스트 전체 갯수 내려달라고 요청해놓았습니다
- const postsCount = 0
-
return (
새로운 상품
- {postsCount > 0 ? (
+ {POSTS_COUNTS_MOCK > 0 ? (
{
- const getCategoriesQuery = useGetCategoriesQuery()
const router = useRouter()
const searchKeyword = useAtomValue(searchKeywordAtom)
const [searchOptions, setSearchOptions] = useState({
@@ -67,64 +62,49 @@ const ResultPage: NextPage = ({
searchKeyword: currentKeyword
})
- const categories =
- getCategoriesQuery.data?.map(({ code, name }) => ({ code, name })) || []
-
const infinitePosts = useGetInfinitePostsQuery({
lastId: null,
- limit: DEFAULT_POST_PAGE_NUMBER,
+ limit: DEFAULT_PER_PAGE,
...searchParams
})
- // TODO: 포스트 전체 갯수 내려달라고 요청해놓았습니다
- const postsCount = 0
+ const searchByResult = ({ priceRange, ...params }: SearchOptionsState) => {
+ router.push(
+ `/result?${toQueryString({
+ ...params,
+ minPrice: priceRange.min,
+ maxPrice: priceRange.max
+ })}`
+ )
+ }
const handleChangeSearchOptions: OnChangeSearchOptions = (name, value) => {
- const nextSearchOptions = {
+ const newSearchOptions = {
...searchOptions,
[name]: value
}
- setSearchOptions(nextSearchOptions)
- router.push(`/result?${toQueryString(searchParams)}`)
+ setSearchOptions(newSearchOptions)
+ searchByResult(newSearchOptions)
}
return (
-
-
- handleChangeSearchOptions('category', code)
- }
- />
-
-
- {postsCount > 0 ? (
-
- ) : (
-
- 검색 결과 없음
-
- 찾으시는 검색 결과가 없어요
-
-
- )}
)
@@ -151,33 +131,4 @@ const Layout = styled.div`
margin-top: 68px;
`
-const CategorySliderWrapper = styled.div`
- /* TODO: useMedia를 사용한 조건부 렌더링시 hydration 에러가 발생해 스타일로 우선 적용 했습니다. */
- ${({ theme }) => theme.mediaQuery.tablet} {
- display: none;
- }
-`
-
-const Placeholder = styled.div`
- width: 100%;
- height: 100%;
- margin: 120px 0;
-
- text-align: center;
-`
-
-const PlaceholderTitle = styled.p`
- margin-bottom: 8px;
-
- ${({ theme }) => theme.fonts.subtitle01B}
-`
-
-const PlaceholderDescription = styled.p`
- ${({ theme }) => css`
- color: ${theme.colors.grayScale70};
-
- ${theme.fonts.body01M};
- `}
-`
-
export default ResultPage
diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts
index 3b13e510..cecf87df 100644
--- a/src/utils/common/index.ts
+++ b/src/utils/common/index.ts
@@ -2,7 +2,7 @@ export const noop = (): void => undefined
export const find = (
arr: T,
- target: UnknownObject
+ target: UnknownObject
): T extends readonly any[] ? ValueOf : undefined => {
const result = arr.find(item => {
const splittedArr = splitObject(item)
diff --git a/src/utils/format/index.ts b/src/utils/format/index.ts
index fb3a31e3..5ee8f3a0 100644
--- a/src/utils/format/index.ts
+++ b/src/utils/format/index.ts
@@ -53,12 +53,14 @@ export const toLocaleCurrency = (value: number): string => {
}
export const toQueryString = (object: {
- [key: string]: string | number
+ [key: string]: string | number | undefined
}): URLSearchParams => {
const searchParams = new URLSearchParams()
Object.entries(object).forEach(([key, value]) => {
- searchParams.set(String(key), String(value))
+ if (value) {
+ searchParams.set(String(key), String(value))
+ }
})
return searchParams