diff --git a/src/components/RealTimeKeyword/RealTimeKeyword.constant.ts b/src/components/RealTimeKeyword/RealTimeKeyword.constant.ts new file mode 100644 index 00000000..85240bef --- /dev/null +++ b/src/components/RealTimeKeyword/RealTimeKeyword.constant.ts @@ -0,0 +1,34 @@ +import { RealTimeKeywordStyleProps } from '@/components/RealTimeKeyword/RealTimeKeyword.style.ts'; + +export type VariantType = 'home' | 'search'; + +export const variantStyles: { [key in VariantType]: RealTimeKeywordStyleProps } = { + home: { + $containerPadding: '1rem', + $containerWidth: '32.5rem', + $containerHeight: 'auto', + $titleContainerPadding: '0.3125rem 0.5rem', + $titleContainerMarginBottom: '0.5rem', + $updateDateTypo: 'caption2', + $columnCount: 2, + $keywordWidth: '7.375rem', + $imageWidth: '10.3125rem', + $imageHeight: '6.875rem', + $imageTop: '-1.125rem', + $imageRight: '0.9375rem', + }, + search: { + $containerPadding: '1.25rem', + $containerWidth: '25rem', + $containerHeight: '38.7rem', + $titleContainerPadding: '0.5rem', + $titleContainerMarginBottom: '0.75rem', + $updateDateTypo: 'body3', + $columnCount: 1, + $keywordWidth: 'auto', + $imageWidth: '12.6875rem', + $imageHeight: '8.5rem', + $imageTop: '-3.125rem', + $imageRight: '0', + }, +}; diff --git a/src/components/RealTimeKeyword/RealTimeKeyword.style.ts b/src/components/RealTimeKeyword/RealTimeKeyword.style.ts new file mode 100644 index 00000000..d79563c2 --- /dev/null +++ b/src/components/RealTimeKeyword/RealTimeKeyword.style.ts @@ -0,0 +1,121 @@ +import { Typo } from '@yourssu/design-system-react'; +import styled from 'styled-components'; + +interface StyledContainerProps { + $containerPadding: string; + $containerWidth: string; + $containerHeight: string; +} + +export const StyledContainer = styled.div` + position: relative; + padding: ${({ $containerPadding }) => $containerPadding}; + width: ${({ $containerWidth }) => $containerWidth}; + height: ${({ $containerHeight }) => $containerHeight}; + border-radius: 0.75rem; + border: 1px solid ${({ theme }) => theme.color.borderNormal}; + background: ${({ theme }) => theme.color.bgNormal}; +`; + +interface StyledTitleContainerProps { + $titleContainerPadding: string; + $titleContainerMarginBottom: string; +} + +export const StyledTitleContainer = styled.div` + padding: ${({ $titleContainerPadding }) => $titleContainerPadding}; + margin-bottom: ${({ $titleContainerMarginBottom }) => $titleContainerMarginBottom}; +`; + +export const StyledTitle = styled.div` + margin-bottom: 0.5rem; + white-space: pre-wrap; + color: ${({ theme }) => theme.color.textPrimary}; + ${({ theme }) => theme.typo.subtitle3}; +`; + +interface StyledUpdateDateProps { + $updateDateTypo: Typo; +} + +export const StyledUpdateDate = styled.div` + color: ${({ theme }) => theme.color.textTertiary}; + ${({ theme, $updateDateTypo }) => theme.typo[$updateDateTypo]}; +`; + +interface StyledListProps { + $columnCount: number; +} + +export const StyledList = styled.div` + display: grid; + grid-template-rows: ${({ $columnCount }) => `repeat(${10 / $columnCount}, 1fr)`}; + grid-template-columns: ${({ $columnCount }) => `repeat(${$columnCount}, 1fr)`}; + grid-auto-flow: column; + grid-column-gap: 0.5rem; +`; + +export const StyledListItem = styled.li` + display: flex; + padding: 0.625rem 0.75rem; + justify-content: space-between; + align-items: center; + align-self: stretch; + background: ${({ theme }) => theme.color.bgNormal}; + &:hover { + background: ${({ theme }) => theme.color.bgRecomment}; + } +`; + +export const StyledListItemText = styled.span` + display: flex; + align-items: center; + gap: 8px; +`; + +interface StyledRankProps { + $rank: number; +} + +export const StyledListItemRanking = styled.span` + color: ${({ $rank, theme }) => ($rank < 4 ? '#423FCC' : theme.color.textTertiary)}; + ${({ theme }) => theme.typo.subtitle4}; + width: 1.5rem; +`; + +interface StyledListItemKeywordProps { + $keywordWidth: string; +} + +export const StyledListItemKeyword = styled.p` + width: ${({ $keywordWidth }) => $keywordWidth}; + color: ${({ theme }) => theme.color.textPrimary}; + ${({ theme }) => theme.typo.body1}; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +interface StyledImageProps { + $imageWidth: string; + $imageHeight: string; + $imageTop: string; + $imageRight: string; +} + +export const StyledImage = styled.img` + width: ${({ $imageWidth }) => $imageWidth}; + height: ${({ $imageHeight }) => $imageHeight}; + + position: absolute; + right: ${({ $imageRight }) => $imageRight}; + top: ${({ $imageTop }) => $imageTop}; +`; + +export type RealTimeKeywordStyleProps = StyledContainerProps & + StyledTitleContainerProps & + StyledUpdateDateProps & + StyledListProps & + StyledListItemKeywordProps & + StyledImageProps; diff --git a/src/components/RealTimeKeyword/RealTimeKeyword.tsx b/src/components/RealTimeKeyword/RealTimeKeyword.tsx new file mode 100644 index 00000000..8e33df9a --- /dev/null +++ b/src/components/RealTimeKeyword/RealTimeKeyword.tsx @@ -0,0 +1,93 @@ +import { Suspense } from 'react'; + +import { IcSearchLine } from '@yourssu/design-system-react'; +import { Link } from 'react-router-dom'; + +import RealTimeKeywordImage from '@/assets/realTimeKeyword.webp'; +import { + variantStyles, + VariantType, +} from '@/components/RealTimeKeyword/RealTimeKeyword.constant.ts'; +import { + StyledContainer, + StyledTitleContainer, + StyledImage, + StyledTitle, + StyledUpdateDate, + StyledList, + StyledListItemRanking, + StyledListItemKeyword, + StyledListItem, + StyledListItemText, +} from '@/components/RealTimeKeyword/RealTimeKeyword.style.ts'; +import { useGetRealTimeKeyword } from '@/search/hooks/useGetRealTimeKeyword.ts'; +import { formatDateTime } from '@/utils/formatDateTime.ts'; + +interface RealTimeKeywordProps { + variant: VariantType; +} + +export const RealTimeKeyword = ({ variant }: RealTimeKeywordProps) => { + const { data } = useGetRealTimeKeyword(); + const { + $containerPadding, + $containerWidth, + $containerHeight, + $titleContainerPadding, + $titleContainerMarginBottom, + $updateDateTypo, + $columnCount, + $keywordWidth, + $imageWidth, + $imageHeight, + $imageTop, + $imageRight, + } = variantStyles[variant]; + + return ( + + 연결 중입니다. + } + > + + {'숨쉬듯이\n검색한 키워드'} + {`${formatDateTime(data.basedTime)} 기준`} + + + + {data.queries.map((value, index) => ( + + + + {index + 1} + + {value.query} + + + + + + ))} + + + + ); +}; diff --git a/src/drawer/apis/getBookMarked.ts b/src/drawer/apis/getBookMarked.ts index 78803625..b7d4564c 100644 --- a/src/drawer/apis/getBookMarked.ts +++ b/src/drawer/apis/getBookMarked.ts @@ -1,9 +1,9 @@ import { soomsilClient } from '@/apis'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; import { ProductResponses } from '../types/product.type'; -export const getBookmarked = async ({ responseType, page }: RankingRequestParams) => { +export const getBookmarked = async ({ responseType, page }: ProductRequestParams) => { const response = await soomsilClient.get('/v2/drawer/my-bookmarked', { params: { responseType, diff --git a/src/drawer/apis/getMyProduct.ts b/src/drawer/apis/getMyProduct.ts index c83566c9..19a1093f 100644 --- a/src/drawer/apis/getMyProduct.ts +++ b/src/drawer/apis/getMyProduct.ts @@ -1,9 +1,9 @@ import { soomsilClient } from '@/apis'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; import { ProductResponses } from '../types/product.type'; -export const getMyProduct = async ({ responseType, page }: RankingRequestParams) => { +export const getMyProduct = async ({ responseType, page }: ProductRequestParams) => { const response = await soomsilClient.get('/v2/drawer/my-registered', { params: { responseType, diff --git a/src/drawer/apis/getNewRelease.ts b/src/drawer/apis/getNewRelease.ts index b89e9379..6efea4af 100644 --- a/src/drawer/apis/getNewRelease.ts +++ b/src/drawer/apis/getNewRelease.ts @@ -1,9 +1,9 @@ import { soomsilClient } from '@/apis'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; import { ProductResponses } from '../types/product.type'; -export const getNewRelease = async ({ responseType, category, page }: RankingRequestParams) => { +export const getNewRelease = async ({ responseType, category, page }: ProductRequestParams) => { const response = await soomsilClient.get('/v2/drawer/new-release', { params: { responseType, diff --git a/src/drawer/apis/getRanking.ts b/src/drawer/apis/getRanking.ts index 2f9a3430..e9e11c08 100644 --- a/src/drawer/apis/getRanking.ts +++ b/src/drawer/apis/getRanking.ts @@ -1,9 +1,9 @@ import { soomsilClient } from '@/apis'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; import { ProductResponses } from '../types/product.type'; -export const getRanking = async ({ responseType, category, page }: RankingRequestParams) => { +export const getRanking = async ({ responseType, category, page }: ProductRequestParams) => { const response = await soomsilClient.get('/v2/drawer/rank', { params: { responseType, diff --git a/src/drawer/constants/page.constant.ts b/src/drawer/constants/page.constant.ts new file mode 100644 index 00000000..720b59be --- /dev/null +++ b/src/drawer/constants/page.constant.ts @@ -0,0 +1 @@ +export const PRODUCTS_PER_PAGE = 21; diff --git a/src/drawer/hooks/useGetBookMarked.ts b/src/drawer/hooks/useGetBookMarked.ts index 3f3e3122..8e512286 100644 --- a/src/drawer/hooks/useGetBookMarked.ts +++ b/src/drawer/hooks/useGetBookMarked.ts @@ -1,15 +1,29 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; +import { InfiniteData, useSuspenseInfiniteQuery } from '@tanstack/react-query'; import { getBookmarked } from '../apis/getBookMarked'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; +import { ProductResponses, ProductResult } from '../types/product.type'; -export const useGetBookmarked = ({ responseType, page }: RankingRequestParams) => { - return useSuspenseQuery({ - queryKey: ['bookmarked', { responseType, page }], - queryFn: () => { - return getBookmarked({ responseType, page }); +type ProductRequestParamsWithoutPage = Omit; + +export const useGetBookmarked = ({ responseType }: ProductRequestParamsWithoutPage) => { + return useSuspenseInfiniteQuery< + ProductResponses[], + Error, + InfiniteData, + string[], + number + >({ + queryKey: ['bookmarked', responseType], + queryFn: ({ pageParam }) => + getBookmarked({ responseType, page: pageParam }).then((data) => data.productList) as Promise< + ProductResponses[] + >, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + return lastPage.length === PRODUCTS_PER_PAGE ? allPages.length : undefined; }, retry: false, - select: (data) => data.productList, }); }; diff --git a/src/drawer/hooks/useGetMyRegistered.ts b/src/drawer/hooks/useGetMyRegistered.ts index de0e35b1..acfec154 100644 --- a/src/drawer/hooks/useGetMyRegistered.ts +++ b/src/drawer/hooks/useGetMyRegistered.ts @@ -1,15 +1,29 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; +import { InfiniteData, useSuspenseInfiniteQuery } from '@tanstack/react-query'; import { getMyProduct } from '../apis/getMyProduct'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; +import { ProductResponses, ProductResult } from '../types/product.type'; -export const useGetMyRegistered = ({ responseType, page }: RankingRequestParams) => { - return useSuspenseQuery({ - queryKey: ['myRegistered', { responseType, page }], - queryFn: () => { - return getMyProduct({ responseType, page }); +type ProductRequestParamsWithoutPage = Omit; + +export const useGetMyRegistered = ({ responseType }: ProductRequestParamsWithoutPage) => { + return useSuspenseInfiniteQuery< + ProductResponses[], + Error, + InfiniteData, + string[], + number + >({ + queryKey: ['myRegistered', responseType], + queryFn: ({ pageParam }) => + getMyProduct({ responseType, page: pageParam }).then((data) => data.productList) as Promise< + ProductResponses[] + >, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + return lastPage.length === PRODUCTS_PER_PAGE ? allPages.length : undefined; }, retry: false, - select: (data) => data.productList, }); }; diff --git a/src/drawer/hooks/useGetNewRelease.ts b/src/drawer/hooks/useGetNewRelease.ts index 6301c487..9519e149 100644 --- a/src/drawer/hooks/useGetNewRelease.ts +++ b/src/drawer/hooks/useGetNewRelease.ts @@ -1,19 +1,31 @@ -import { useQuery } from '@tanstack/react-query'; +import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query'; import { getNewRelease } from '@/drawer/apis/getNewRelease'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; +import { ProductResponses, ProductResult } from '../types/product.type'; -export const useGetNewRelease = ({ responseType, category, page }: RankingRequestParams) => { - return useQuery({ - queryKey: ['newReleases', { responseType, category, page }], - queryFn: () => { - return getNewRelease({ +type ProductRequestParamsWithoutPage = Omit; + +export const useGetNewRelease = ({ responseType, category }: ProductRequestParamsWithoutPage) => { + return useInfiniteQuery< + ProductResponses[], + Error, + InfiniteData, + string[], + number + >({ + queryKey: ['newReleases', responseType, category ?? ''], + queryFn: ({ pageParam }) => + getNewRelease({ responseType, category, - page, - }); + page: pageParam, + }).then((data) => data.productList) as Promise, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + return lastPage.length === PRODUCTS_PER_PAGE ? allPages.length : undefined; }, - select: (data) => data.productList, }); }; diff --git a/src/drawer/hooks/useGetProductByProvider.ts b/src/drawer/hooks/useGetProductByProvider.ts index 46f75a36..738d08b1 100644 --- a/src/drawer/hooks/useGetProductByProvider.ts +++ b/src/drawer/hooks/useGetProductByProvider.ts @@ -4,10 +4,9 @@ import { useRecoilValue } from 'recoil'; import { CategoryState } from '@/drawer/recoil/CategoryState'; import { getProductByProvider } from '../apis/getProductByProvider'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; import { ProviderProductResponses } from '../types/product.type'; -const PRODUCTS_PER_PAGE = 21; - export const useGetProductByProvider = ({ providerId }: { providerId: string }) => { const selectedCategory = useRecoilValue(CategoryState); diff --git a/src/drawer/hooks/useGetStarRank.ts b/src/drawer/hooks/useGetStarRank.ts index 9248b936..71fb6d50 100644 --- a/src/drawer/hooks/useGetStarRank.ts +++ b/src/drawer/hooks/useGetStarRank.ts @@ -1,18 +1,30 @@ -import { useQuery } from '@tanstack/react-query'; +import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query'; import { getRanking } from '../apis/getRanking'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; +import { ProductResponses, ProductResult } from '../types/product.type'; -export const useGetStarRank = ({ responseType, category, page }: RankingRequestParams) => { - return useQuery({ - queryKey: ['getRanking', { responseType, category, page }], - queryFn: () => { - return getRanking({ +type ProductRequestParamsWithoutPage = Omit; + +export const useGetStarRank = ({ responseType, category }: ProductRequestParamsWithoutPage) => { + return useInfiniteQuery< + ProductResponses[], + Error, + InfiniteData, + string[], + number + >({ + queryKey: ['getRanking', responseType, category ?? ''], + queryFn: ({ pageParam }) => + getRanking({ responseType, category, - page, - }); + page: pageParam, + }).then((data) => data.productList) as Promise, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + return lastPage.length === PRODUCTS_PER_PAGE ? allPages.length : undefined; }, - select: (data) => data.productList, }); }; diff --git a/src/drawer/pages/MyDrawer/MyDrawer.tsx b/src/drawer/pages/MyDrawer/MyDrawer.tsx index 40cdf930..06e68032 100644 --- a/src/drawer/pages/MyDrawer/MyDrawer.tsx +++ b/src/drawer/pages/MyDrawer/MyDrawer.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useCallback, useRef } from 'react'; import { useSearchParams } from 'react-router-dom'; @@ -16,8 +16,21 @@ export type TabType = 'STAR' | 'MYDRAWER'; export const MyDrawer = () => { const [searchParams, setSearchParams] = useSearchParams(); - const { data: bookmarkedData } = useGetBookmarked({ responseType: 'WEB', page: 0 }); - const { data: myProductData } = useGetMyRegistered({ responseType: 'WEB', page: 0 }); + + const { + data: bookmarkedData, + fetchNextPage: fetchNextBookmarkedPage, + hasNextPage: hasNextBookmarkedPage, + isLoading: isLoadingBookmarked, + } = useGetBookmarked({ responseType: 'WEB' }); + + const { + data: myProductData, + fetchNextPage: fetchNextMyProductPage, + hasNextPage: hasNextMyProductPage, + isLoading: isLoadingMyProduct, + } = useGetMyRegistered({ responseType: 'WEB' }); + const initialTab = searchParams.get('tab'); const [currentTab, setCurrentTab] = useState( @@ -25,6 +38,26 @@ export const MyDrawer = () => { ); const drawerData = currentTab === 'STAR' ? bookmarkedData : myProductData; + const fetchNextPage = currentTab === 'STAR' ? fetchNextBookmarkedPage : fetchNextMyProductPage; + const hasNextPage = currentTab === 'STAR' ? hasNextBookmarkedPage : hasNextMyProductPage; + const isLoading = currentTab === 'STAR' ? isLoadingBookmarked : isLoadingMyProduct; + + const observer = useRef(); + + const lastElementRef = useCallback( + (node: HTMLDivElement) => { + if (isLoading) return; + + if (observer.current) observer.current.disconnect(); + observer.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && hasNextPage) { + fetchNextPage(); + } + }); + if (node) observer.current.observe(node); + }, + [isLoading, hasNextPage, fetchNextPage] + ); function isTabType(tab: string | null): tab is TabType { return tab === 'STAR' || tab === 'MYDRAWER'; @@ -55,13 +88,14 @@ export const MyDrawer = () => { /> - {drawerData.length > 0 ? ( + {drawerData.pages[0].length === 0 ? ( + + ) : ( <> {TAB_DESCRIPTION[currentTab]} - + page)} type={currentTab} /> +
- ) : ( - )} ); diff --git a/src/drawer/pages/NewRelease/NewRelease.tsx b/src/drawer/pages/NewRelease/NewRelease.tsx index 45c2bbb7..f81c5be2 100644 --- a/src/drawer/pages/NewRelease/NewRelease.tsx +++ b/src/drawer/pages/NewRelease/NewRelease.tsx @@ -1,3 +1,5 @@ +import { useCallback, useRef } from 'react'; + import { useRecoilValue } from 'recoil'; import { CategoryDropdownMenu } from '@/drawer/components/Category/CategoryDropdownMenu/CategoryDropdownMenu'; @@ -20,11 +22,33 @@ import { export const NewRelease = () => { const selectedCategory = useRecoilValue(CategoryState); - const { data: newReleases } = useGetNewRelease({ + const { + data: newReleases, + isLoading, + fetchNextPage, + hasNextPage, + } = useGetNewRelease({ responseType: 'WEB', category: selectedCategory, - page: 0, }); + + const observer = useRef(); + + const lastElementRef = useCallback( + (node: HTMLDivElement) => { + if (isLoading) return; + + if (observer.current) observer.current.disconnect(); + observer.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && hasNextPage) { + fetchNextPage(); + } + }); + if (node) observer.current.observe(node); + }, + [isLoading, hasNextPage] + ); + const isSmallDesktop = useMediaQuery(SMALL_DESKTOP_MEDIA_QUERY); return ( @@ -41,18 +65,23 @@ export const NewRelease = () => { {newReleases && - newReleases.map((product) => ( - - ))} + newReleases.pages.flat().map((product, productIndex, flatArray) => { + const isLastProduct = productIndex === flatArray.length - 1; + return ( +
+ + {isLastProduct &&
} +
+ ); + })}
diff --git a/src/drawer/pages/Ranking/Ranking.tsx b/src/drawer/pages/Ranking/Ranking.tsx index b173bb09..1c1b61b4 100644 --- a/src/drawer/pages/Ranking/Ranking.tsx +++ b/src/drawer/pages/Ranking/Ranking.tsx @@ -30,12 +30,10 @@ export const Ranking = () => { const { data: newReleases } = useGetNewRelease({ responseType: 'WEB', category: selectedCategory, - page: 0, }); const { data: rankings } = useGetStarRank({ responseType: 'WEB', category: selectedCategory, - page: 0, }); const isSmallDesktop = useMediaQuery(SMALL_DESKTOP_MEDIA_QUERY); @@ -63,7 +61,7 @@ export const Ranking = () => { {rankings && - rankings + rankings.pages[0] .slice(0, 3) .map((product) => ( { {newReleases && - newReleases + newReleases.pages[0] .slice(0, 3) .map((product) => ( { const selectedCategory = useRecoilValue(CategoryState); - const { data: rankings } = useGetStarRank({ + + const { + data: rankings, + isLoading, + fetchNextPage, + hasNextPage, + } = useGetStarRank({ responseType: 'WEB', category: selectedCategory, - page: 0, }); + + const observer = useRef(); + + const lastElementRef = useCallback( + (node: HTMLDivElement) => { + if (isLoading) return; + + if (observer.current) observer.current.disconnect(); + observer.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && hasNextPage) { + fetchNextPage(); + } + }); + if (node) observer.current.observe(node); + }, + [isLoading, hasNextPage] + ); + const isSmallDesktop = useMediaQuery(SMALL_DESKTOP_MEDIA_QUERY); return ( @@ -41,18 +66,23 @@ export const StarRanking = () => { {rankings && - rankings.map((product) => ( - - ))} + rankings.pages.flat().map((product, productIndex, flatArray) => { + const isLastProduct = productIndex === flatArray.length - 1; + return ( +
+ + {isLastProduct &&
} +
+ ); + })}
diff --git a/src/drawer/types/RankingRequestParams.type.ts b/src/drawer/types/ProductRequestParams.type.ts similarity index 61% rename from src/drawer/types/RankingRequestParams.type.ts rename to src/drawer/types/ProductRequestParams.type.ts index f4eeee84..8456eb67 100644 --- a/src/drawer/types/RankingRequestParams.type.ts +++ b/src/drawer/types/ProductRequestParams.type.ts @@ -1,4 +1,4 @@ -export interface RankingRequestParams { +export interface ProductRequestParams { responseType: string; category?: string; page: number; diff --git a/src/home/components/SearchKeyword/SearchKeyword.style.ts b/src/home/components/SearchKeyword/SearchKeyword.style.ts deleted file mode 100644 index 026b0ad6..00000000 --- a/src/home/components/SearchKeyword/SearchKeyword.style.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { ListItem } from '@yourssu/design-system-react'; -import styled from 'styled-components'; - -interface StyledRankProps { - $rank: number; -} - -export const StyledContainer = styled.div` - width: 32.5rem; - position: relative; - padding: 0.9375rem; - - border-radius: 0.75rem; - border: 1px solid ${({ theme }) => theme.color.borderNormal}; - - background: ${({ theme }) => theme.color.bgNormal}; -`; - -export const StyledTitleContainer = styled.div` - padding: 0.3125rem 0.5rem; - margin-bottom: 0.5rem; -`; - -export const StyledTitle = styled.div` - margin-bottom: 0.5rem; - - color: ${({ theme }) => theme.color.textPrimary}; - ${({ theme }) => theme.typo.subtitle3}; -`; - -export const StyledUpdateDate = styled.div` - height: 0.875rem; - - color: ${({ theme }) => theme.color.textTertiary}; - ${({ theme }) => theme.typo.caption2} -`; - -export const StyledListContainer = styled.div` - display: grid; - grid-template-rows: repeat(5, 1fr); - grid-template-columns: 1fr 1fr; - grid-auto-flow: column; - grid-column-gap: 0.5rem; -`; - -export const StyledListItem = styled(ListItem)` - min-height: 2.9375rem; - padding: 0.625rem 0.75rem; -`; - -export const StyledRank = styled.span` - color: ${(props) => (props.$rank < 4 ? '#8A2AC5' : props.theme.color.textTertiary)}; - - // YDS 지정이 되어 있지 않아 임시로 작업했습니다. - font-family: 'Spoqa Han Sans Neo'; - font-size: 1.125rem; - font-style: normal; - font-weight: 500; - line-height: 130%; -`; - -export const StyledListItemKeyword = styled.div` - width: 7.375rem; - height: 1.6875rem; - - color: ${({ theme }) => theme.color.textPrimary}; - ${({ theme }) => theme.typo.body1}; - - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -export const StyledImage = styled.img` - width: 10.3125rem; - height: 6.875rem; - - position: absolute; - right: 0.9375rem; - top: -1.125rem; -`; diff --git a/src/home/components/SearchKeyword/SearchKeyword.tsx b/src/home/components/SearchKeyword/SearchKeyword.tsx deleted file mode 100644 index 02b9107b..00000000 --- a/src/home/components/SearchKeyword/SearchKeyword.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Suspense } from 'react'; - -import { IcSearchLine, IconContext } from '@yourssu/design-system-react'; -import { Link } from 'react-router-dom'; - -import RealTimeKeywordImage from '@/assets/realTimeKeyword.webp'; -import { useGetRealTimeKeyword } from '@/search/hooks/useGetRealTimeKeyword'; -import { formatDateTime } from '@/utils/formatDateTime'; - -import { - StyledContainer, - StyledImage, - StyledListContainer, - StyledListItem, - StyledListItemKeyword, - StyledRank, - StyledTitle, - StyledTitleContainer, - StyledUpdateDate, -} from './SearchKeyword.style'; - -export const SearchKeyword = () => { - const { data } = useGetRealTimeKeyword(); - - return ( - - 연결 중입니다.}> - - - 숨쉬듯이 -
- 검색된 키워드 -
- {formatDateTime(data.basedTime)} 기준 -
- - - {data.queries.map((value, index) => ( - - {index + 1}} - rightIcon={ - - - - } - width="15rem" - > - {value.query} - - - ))} - -
- ); -}; diff --git a/src/home/pages/Home/Home.tsx b/src/home/pages/Home/Home.tsx index e2fc6b7c..f3c6953d 100644 --- a/src/home/pages/Home/Home.tsx +++ b/src/home/pages/Home/Home.tsx @@ -1,10 +1,10 @@ import { useRecoilValue } from 'recoil'; +import { RealTimeKeyword } from '@/components/RealTimeKeyword/RealTimeKeyword.tsx'; import { DrawerRanking } from '@/home/components/DrawerRanking/DrawerRanking'; import { Header } from '@/home/components/Header/Header'; import { Nav } from '@/home/components/Nav/Nav'; import { Notification } from '@/home/components/Notification/Notification'; -import { SearchKeyword } from '@/home/components/SearchKeyword/SearchKeyword'; import { SocialNetworkService } from '@/home/components/SocialNetworkService/SocialNetworkService'; import { LogInState } from '@/home/recoil/LogInState'; @@ -25,7 +25,7 @@ export const Home = () => { - + diff --git a/src/search/components/RealTimeKeyword/RealTimeKeyword.style.ts b/src/search/components/RealTimeKeyword/RealTimeKeyword.style.ts deleted file mode 100644 index 0dcc9289..00000000 --- a/src/search/components/RealTimeKeyword/RealTimeKeyword.style.ts +++ /dev/null @@ -1,78 +0,0 @@ -import styled from 'styled-components'; - -export const StyledContainer = styled.div` - display: flex; - flex-direction: column; - padding: 1.25rem; - max-width: 25rem; - min-width: 25rem; - height: 38.7rem; - gap: 0.625rem; - border-radius: 0.75rem; - border: 1px solid ${({ theme }) => theme.color.borderNormal}; - background: ${({ theme }) => theme.color.bgNormal}; -`; - -export const StyledHeader = styled.div` - display: flex; - position: relative; - padding: 0.5rem; -`; - -export const StyledHeaderTextSection = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - gap: 0.5rem; -`; - -export const StyledHeaderTitle = styled.span` - color: ${({ theme }) => theme.color.textPrimary}; - - ${({ theme }) => theme.typo.subtitle1}; -`; - -export const StyledHeaderTime = styled.span` - color: ${({ theme }) => theme.color.textTertiary}; - - ${({ theme }) => theme.typo.body2} -`; - -export const StyledHeaderImageSection = styled.img` - width: 12.6875rem; - height: 8.5rem; - - position: absolute; - right: -1.25rem; - top: -3.125rem; -`; - -export const StyledListItemText = styled.p` - color: ${({ theme }) => theme.color.textPrimary}; - - /* PC & Android/body18 */ - font-family: 'Spoqa Han Sans Neo'; - font-size: 1.125rem; - font-style: normal; - font-weight: 400; - line-height: 130%; -`; - -interface StyledListItemRankingProp { - $rankingNumber: number; -} - -export const StyledListItemRanking = styled.span` - color: ${(prop) => (prop.$rankingNumber < 4 ? '#8a2ac5' : '#8E9398')}; - font-family: 'Spoqa Han Sans Neo'; - font-size: 1.155rem; - font-style: normal; - font-weight: 500; - line-height: 130%; - margin-right: 1rem; -`; - -export const StyledList = styled.div` - display: flex; - flex-direction: column; -`; diff --git a/src/search/components/RealTimeKeyword/RealTimeKeyword.tsx b/src/search/components/RealTimeKeyword/RealTimeKeyword.tsx deleted file mode 100644 index f5d760f1..00000000 --- a/src/search/components/RealTimeKeyword/RealTimeKeyword.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Suspense, useEffect, useState } from 'react'; - -import { ListItem, IcSearchLine, IconContext } from '@yourssu/design-system-react'; -import { useNavigate } from 'react-router-dom'; - -import RealTimeKeywordImage from '@/assets/realTimeKeyword.webp'; -import { useGetRealTimeKeyword } from '@/search/hooks/useGetRealTimeKeyword'; -import { formatDateTime } from '@/utils/formatDateTime'; - -import { - StyledContainer, - StyledHeader, - StyledHeaderImageSection, - StyledHeaderTitle, - StyledHeaderTime, - StyledHeaderTextSection, - StyledList, - StyledListItemRanking, - StyledListItemText, -} from './RealTimeKeyword.style'; - -export const RealTimeKeyword = () => { - const navigate = useNavigate(); - - const { data } = useGetRealTimeKeyword(); - - const [time, setTime] = useState(); - - useEffect(() => { - setTime(formatDateTime(data.basedTime)); - }, [data]); - - const handleClickListItem = (event: React.MouseEvent) => { - const queryKeyword = event.currentTarget.innerText.split('\n')[2]; - navigate(`?query=${queryKeyword}`); - }; - - return ( - - 연결 중입니다.}> - - - - 숨쉬듯이 -
- 검색한 키워드 -
- {`${time} 기준`} -
- -
- - {data.queries.map((value, index) => { - return ( - - {index + 1} - - } - /** - * @수정사항 color 색상이 YDS에 없어서 HEX로 넣었습니다. - */ - rightIcon={ - - - - } - style={{ - padding: '0.75rem', - }} - children={{value.query}} - onClick={handleClickListItem} - /> - ); - })} - -
-
- ); -}; diff --git a/src/search/pages/Search/Search.tsx b/src/search/pages/Search/Search.tsx index b97f2532..0c1e03d7 100644 --- a/src/search/pages/Search/Search.tsx +++ b/src/search/pages/Search/Search.tsx @@ -1,9 +1,9 @@ import { ErrorBoundary } from 'react-error-boundary'; import { useSearchParams } from 'react-router-dom'; +import { RealTimeKeyword } from '@/components/RealTimeKeyword/RealTimeKeyword'; import { Spacing } from '@/components/Spacing/Spacing'; import { ErrorFallback } from '@/search/components/FallbackComponent/ErrorFallback'; -import { RealTimeKeyword } from '@/search/components/RealTimeKeyword/RealTimeKeyword'; import { ResultList } from '@/search/components/ResultList/ResultList'; import { SearchBar } from '@/search/components/SearchBar/SearchBar'; import { TotalCount } from '@/search/components/TotalCount/TotalCount'; @@ -44,7 +44,7 @@ export const Search = () => { - +