From 8249b3959a15b4d9f07a8b067120cdc8adb1129f Mon Sep 17 00:00:00 2001 From: TaeeunKim Date: Fri, 8 Sep 2023 17:28:17 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EC=9C=A0?= =?UTF-8?q?=EC=A7=80=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/router/index.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index 8391765a3..4465c3ada 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -22,24 +22,20 @@ import SearchPage from '@/pages/SearchPage'; const router = createBrowserRouter([ { path: '/', - element: , + element: ( + + + + ), errorElement: , children: [ { index: true, - element: ( - - - - ), + element: , }, { path: `${PATH.PRODUCT_LIST}/:category`, - element: ( - - - - ), + element: , }, { path: PATH.RECIPE, @@ -108,7 +104,11 @@ const router = createBrowserRouter([ }, { path: '/', - element: , + element: ( + + + + ), errorElement: , children: [ { From 2f9aa303c58f147e064033613c95f494b21dd849 Mon Sep 17 00:00:00 2001 From: TaeeunKim Date: Wed, 13 Sep 2023 15:57:18 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=ED=99=94=EB=A9=B4=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=EC=9D=B4=20=EB=90=98=EC=97=88=EC=9D=84=20=EB=95=8C=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=9C=84=EC=B9=98=EB=A5=BC=20?= =?UTF-8?q?=EA=B8=B0=EC=96=B5=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/CategoryMenu/CategoryMenu.tsx | 4 +- frontend/src/contexts/CategoryContext.tsx | 11 +++++ frontend/src/hooks/common/index.ts | 1 + .../src/hooks/common/useScrollRestoration.ts | 24 +++++++++ .../src/hooks/context/useCategoryContext.ts | 4 +- frontend/src/pages/ProductListPage.tsx | 49 ++++++++++++------- 6 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 frontend/src/hooks/common/useScrollRestoration.ts diff --git a/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx b/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx index b262b8acd..7ff4ee413 100644 --- a/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx +++ b/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx @@ -2,6 +2,7 @@ import { Button, theme } from '@fun-eat/design-system'; import type { CSSProp } from 'styled-components'; import styled from 'styled-components'; +import { useScrollRestoration } from '@/hooks/common'; import { useCategoryContext } from '@/hooks/context'; import { useCategoryQuery } from '@/hooks/queries/product'; import type { CategoryVariant } from '@/types/common'; @@ -13,9 +14,10 @@ interface CategoryMenuProps { const CategoryMenu = ({ menuVariant }: CategoryMenuProps) => { const { data: categories } = useCategoryQuery(menuVariant); const { categoryIds, selectCategory } = useCategoryContext(); - const currentCategoryId = categoryIds[menuVariant]; + useScrollRestoration(currentCategoryId); + return ( {categories.map((menu) => { diff --git a/frontend/src/contexts/CategoryContext.tsx b/frontend/src/contexts/CategoryContext.tsx index a7ceb895c..00065b18c 100644 --- a/frontend/src/contexts/CategoryContext.tsx +++ b/frontend/src/contexts/CategoryContext.tsx @@ -15,23 +15,34 @@ type CategoryIds = { interface CategoryState { categoryIds: CategoryIds; selectCategory: (menuVariant: string, categoryId: number) => void; + currentTabScroll: { [key: number]: number }; + saveCurrentTabScroll: (categoryId: number, scrollY: number) => void; } export const CategoryContext = createContext({ categoryIds: initialState, selectCategory: () => {}, + currentTabScroll: {}, + saveCurrentTabScroll: () => {}, }); const CategoryProvider = ({ children }: PropsWithChildren) => { const [categoryIds, setCategoryIds] = useState(initialState); + const [currentTabScroll, setCurrentTabScroll] = useState({}); const selectCategory = (menuVariant: string, categoryId: number) => { setCategoryIds((prevCategory) => ({ ...prevCategory, [menuVariant]: categoryId })); }; + const saveCurrentTabScroll = (categoryId: number, scrollY: number) => { + setCurrentTabScroll((prevState) => ({ ...prevState, [categoryId]: scrollY })); + }; + const categoryState: CategoryState = { categoryIds, + currentTabScroll, selectCategory, + saveCurrentTabScroll, }; return {children}; diff --git a/frontend/src/hooks/common/index.ts b/frontend/src/hooks/common/index.ts index 0f4756f2a..d3c8894c0 100644 --- a/frontend/src/hooks/common/index.ts +++ b/frontend/src/hooks/common/index.ts @@ -8,3 +8,4 @@ export { default as useImageUploader } from './useImageUploader'; export { default as useFormData } from './useFormData'; export { default as useTimeout } from './useTimeout'; export { default as useRouteChangeTracker } from './useRouteChangeTracker'; +export { default as useScrollRestoration } from './useScrollRestoration'; diff --git a/frontend/src/hooks/common/useScrollRestoration.ts b/frontend/src/hooks/common/useScrollRestoration.ts new file mode 100644 index 000000000..cfa37c2f9 --- /dev/null +++ b/frontend/src/hooks/common/useScrollRestoration.ts @@ -0,0 +1,24 @@ +import { useEffect } from 'react'; + +import { useCategoryContext } from '../context'; + +const useScrollRestoration = (currentCategoryId: number) => { + const { saveCurrentTabScroll } = useCategoryContext(); + + useEffect(() => { + const mainElement = document.getElementById('main'); + if (!mainElement) return; + + const handleScroll = () => { + saveCurrentTabScroll(currentCategoryId, mainElement?.scrollTop); + }; + + mainElement.addEventListener('scroll', handleScroll); + + return () => { + mainElement.removeEventListener('scroll', handleScroll); + }; + }, [currentCategoryId]); +}; + +export default useScrollRestoration; diff --git a/frontend/src/hooks/context/useCategoryContext.ts b/frontend/src/hooks/context/useCategoryContext.ts index b67a9d0f5..9f7a676f8 100644 --- a/frontend/src/hooks/context/useCategoryContext.ts +++ b/frontend/src/hooks/context/useCategoryContext.ts @@ -3,9 +3,9 @@ import { useContext } from 'react'; import { CategoryContext } from '@/contexts/CategoryContext'; const useCategoryContext = () => { - const { categoryIds, selectCategory } = useContext(CategoryContext); + const { categoryIds, selectCategory, currentTabScroll, saveCurrentTabScroll } = useContext(CategoryContext); - return { categoryIds, selectCategory }; + return { categoryIds, selectCategory, currentTabScroll, saveCurrentTabScroll }; }; export default useCategoryContext; diff --git a/frontend/src/pages/ProductListPage.tsx b/frontend/src/pages/ProductListPage.tsx index 8e7da4b50..2ccff1c2b 100644 --- a/frontend/src/pages/ProductListPage.tsx +++ b/frontend/src/pages/ProductListPage.tsx @@ -1,6 +1,6 @@ import { BottomSheet, Spacing, useBottomSheet } from '@fun-eat/design-system'; import { useQueryErrorResetBoundary } from '@tanstack/react-query'; -import { Suspense } from 'react'; +import { Suspense, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; @@ -18,41 +18,52 @@ import { ProductList } from '@/components/Product'; import { PRODUCT_SORT_OPTIONS } from '@/constants'; import { PATH } from '@/constants/path'; import { useSortOption } from '@/hooks/common'; +import { useCategoryContext } from '@/hooks/context'; import { isCategoryVariant } from '@/types/common'; const PAGE_TITLE = { food: '공통 상품', store: 'PB 상품' }; const ProductListPage = () => { + const { category } = useParams(); + + if (!category || !isCategoryVariant(category)) { + return null; + } + const { ref, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet(); const { selectedOption, selectSortOption } = useSortOption(PRODUCT_SORT_OPTIONS[0]); const { reset } = useQueryErrorResetBoundary(); - const { category } = useParams(); + const { categoryIds, currentTabScroll } = useCategoryContext(); + const currentCategoryId = categoryIds[category]; - if (!category) { - return null; - } + useEffect(() => { + const mainElement = document.getElementById('main'); + if (!mainElement) return; - if (!isCategoryVariant(category)) { - return null; - } + const scrollY = currentTabScroll[currentCategoryId]; + mainElement.scrollTo(0, scrollY); + }, [currentCategoryId]); return ( <>
- - <Spacing size={30} /> - <Suspense fallback={null}> - <CategoryMenu menuVariant={category} /> - </Suspense> - <ErrorBoundary fallback={ErrorComponent} handleReset={reset}> - <Suspense fallback={<Loading />}> + <div style={{ position: 'fixed', background: 'white', top: '60px', width: 'calc(100% - 40px)' }}> + <Title + headingTitle={PAGE_TITLE[category]} + routeDestination={PATH.PRODUCT_LIST + '/' + (category === 'store' ? 'food' : 'store')} + /> + <Spacing size={30} /> + <Suspense fallback={null}> + <CategoryMenu menuVariant={category} /> <SortButtonWrapper> <SortButton option={selectedOption} onClick={handleOpenBottomSheet} /> </SortButtonWrapper> + </Suspense> + </div> + <Spacing size={120} /> + <ErrorBoundary fallback={ErrorComponent} handleReset={reset}> + <Suspense fallback={<Loading />}> <ProductList category={category} selectedOption={selectedOption} /> </Suspense> </ErrorBoundary> @@ -74,5 +85,5 @@ export default ProductListPage; const SortButtonWrapper = styled.div` display: flex; justify-content: flex-end; - margin: 20px 0; + margin-top: 20px; `; From 6b2f28270af8dac589175a7bc2c41d5611e1f1c4 Mon Sep 17 00:00:00 2001 From: TaeeunKim <xodms0309@naver.com> Date: Wed, 13 Sep 2023 19:59:09 +0900 Subject: [PATCH 03/11] =?UTF-8?q?refactor:=20hook=20=EB=B6=88=EB=9F=AC?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx | 3 --- frontend/src/components/Product/ProductList/ProductList.tsx | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx b/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx index 7ff4ee413..3eb1f41df 100644 --- a/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx +++ b/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx @@ -2,7 +2,6 @@ import { Button, theme } from '@fun-eat/design-system'; import type { CSSProp } from 'styled-components'; import styled from 'styled-components'; -import { useScrollRestoration } from '@/hooks/common'; import { useCategoryContext } from '@/hooks/context'; import { useCategoryQuery } from '@/hooks/queries/product'; import type { CategoryVariant } from '@/types/common'; @@ -16,8 +15,6 @@ const CategoryMenu = ({ menuVariant }: CategoryMenuProps) => { const { categoryIds, selectCategory } = useCategoryContext(); const currentCategoryId = categoryIds[menuVariant]; - useScrollRestoration(currentCategoryId); - return ( <CategoryMenuContainer> {categories.map((menu) => { diff --git a/frontend/src/components/Product/ProductList/ProductList.tsx b/frontend/src/components/Product/ProductList/ProductList.tsx index 63e2cbaac..cb6572bea 100644 --- a/frontend/src/components/Product/ProductList/ProductList.tsx +++ b/frontend/src/components/Product/ProductList/ProductList.tsx @@ -6,7 +6,7 @@ import styled from 'styled-components'; import ProductItem from '../ProductItem/ProductItem'; import { PATH } from '@/constants/path'; -import { useIntersectionObserver } from '@/hooks/common'; +import { useIntersectionObserver, useScrollRestoration } from '@/hooks/common'; import { useCategoryContext } from '@/hooks/context'; import { useInfiniteProductsQuery } from '@/hooks/queries/product'; import type { CategoryVariant, SortOption } from '@/types/common'; @@ -31,6 +31,7 @@ const ProductList = ({ category, isHomePage, selectedOption }: ProductListProps) const productsToDisplay = displaySlice(isHomePage, productList); useIntersectionObserver<HTMLDivElement>(fetchNextPage, scrollRef, hasNextPage); + useScrollRestoration(categoryIds[category]); return ( <> From 3d935a676446fe2cc63f9906f977890b0dbb9c71 Mon Sep 17 00:00:00 2001 From: TaeeunKim <xodms0309@naver.com> Date: Wed, 13 Sep 2023 20:08:45 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=EC=97=90=20=EB=94=94=EB=B0=94?= =?UTF-8?q?=EC=9A=B4=EC=8A=A4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/hooks/common/useScrollRestoration.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/src/hooks/common/useScrollRestoration.ts b/frontend/src/hooks/common/useScrollRestoration.ts index cfa37c2f9..42792f7bc 100644 --- a/frontend/src/hooks/common/useScrollRestoration.ts +++ b/frontend/src/hooks/common/useScrollRestoration.ts @@ -1,22 +1,28 @@ import { useEffect } from 'react'; +import useTimeout from './useTimeout'; import { useCategoryContext } from '../context'; const useScrollRestoration = (currentCategoryId: number) => { const { saveCurrentTabScroll } = useCategoryContext(); - useEffect(() => { + const handleScroll = () => { const mainElement = document.getElementById('main'); if (!mainElement) return; - const handleScroll = () => { - saveCurrentTabScroll(currentCategoryId, mainElement?.scrollTop); - }; + saveCurrentTabScroll(currentCategoryId, mainElement.scrollTop); + }; + + const [timeoutFn] = useTimeout(handleScroll, 300); + + useEffect(() => { + const mainElement = document.getElementById('main'); + if (!mainElement) return; - mainElement.addEventListener('scroll', handleScroll); + mainElement.addEventListener('scroll', timeoutFn); return () => { - mainElement.removeEventListener('scroll', handleScroll); + mainElement.removeEventListener('scroll', timeoutFn); }; }, [currentCategoryId]); }; From 420c5c44403ce56da1f2151933ae8d75f08126f1 Mon Sep 17 00:00:00 2001 From: TaeeunKim <xodms0309@naver.com> Date: Wed, 13 Sep 2023 20:12:50 +0900 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20spacing=20=ED=81=AC=EA=B8=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ProductListPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/ProductListPage.tsx b/frontend/src/pages/ProductListPage.tsx index 2ccff1c2b..f494c6d93 100644 --- a/frontend/src/pages/ProductListPage.tsx +++ b/frontend/src/pages/ProductListPage.tsx @@ -61,7 +61,7 @@ const ProductListPage = () => { </SortButtonWrapper> </Suspense> </div> - <Spacing size={120} /> + <Spacing size={140} /> <ErrorBoundary fallback={ErrorComponent} handleReset={reset}> <Suspense fallback={<Loading />}> <ProductList category={category} selectedOption={selectedOption} /> From bc8bb769980cd764cde07dd20160ac90ebc2d853 Mon Sep 17 00:00:00 2001 From: TaeeunKim <xodms0309@naver.com> Date: Thu, 14 Sep 2023 13:41:04 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor:=20default=20layout=EC=9D=98=20?= =?UTF-8?q?=EC=9C=84=EC=95=84=EB=9E=98=20padding=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Layout/DefaultLayout.tsx | 2 +- frontend/src/pages/HomePage.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Layout/DefaultLayout.tsx b/frontend/src/components/Layout/DefaultLayout.tsx index fe6391912..4daf634ee 100644 --- a/frontend/src/components/Layout/DefaultLayout.tsx +++ b/frontend/src/components/Layout/DefaultLayout.tsx @@ -25,7 +25,7 @@ const DefaultLayoutContainer = styled.div` const MainWrapper = styled.main` position: relative; height: calc(100% - 120px); - padding: 20px; + padding: 0 20px; overflow-x: hidden; overflow-y: auto; `; diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 9048e1492..9da58fc2f 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -90,6 +90,7 @@ const HomePage = () => { </Suspense> </ErrorBoundary> </section> + <Spacing size={36} /> <ScrollButton /> </> ); From 40954dc80fd0829fc41eb7066f83eab2d47e1063 Mon Sep 17 00:00:00 2001 From: TaeeunKim <xodms0309@naver.com> Date: Thu, 14 Sep 2023 13:44:23 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor:=20ref=EC=97=90=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EA=B1=B0=EB=8A=94=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Product/ProductList/ProductList.tsx | 2 - .../src/hooks/common/useScrollRestoration.ts | 16 +++--- frontend/src/pages/ProductListPage.tsx | 52 +++++++++++-------- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/Product/ProductList/ProductList.tsx b/frontend/src/components/Product/ProductList/ProductList.tsx index cb6572bea..86d22cd8a 100644 --- a/frontend/src/components/Product/ProductList/ProductList.tsx +++ b/frontend/src/components/Product/ProductList/ProductList.tsx @@ -31,8 +31,6 @@ const ProductList = ({ category, isHomePage, selectedOption }: ProductListProps) const productsToDisplay = displaySlice(isHomePage, productList); useIntersectionObserver<HTMLDivElement>(fetchNextPage, scrollRef, hasNextPage); - useScrollRestoration(categoryIds[category]); - return ( <> <ProductListContainer> diff --git a/frontend/src/hooks/common/useScrollRestoration.ts b/frontend/src/hooks/common/useScrollRestoration.ts index 42792f7bc..75e1a68e3 100644 --- a/frontend/src/hooks/common/useScrollRestoration.ts +++ b/frontend/src/hooks/common/useScrollRestoration.ts @@ -1,28 +1,26 @@ +import type { RefObject } from 'react'; import { useEffect } from 'react'; import useTimeout from './useTimeout'; import { useCategoryContext } from '../context'; -const useScrollRestoration = (currentCategoryId: number) => { +const useScrollRestoration = (currentCategoryId: number, ref: RefObject<HTMLElement>) => { const { saveCurrentTabScroll } = useCategoryContext(); const handleScroll = () => { - const mainElement = document.getElementById('main'); - if (!mainElement) return; - - saveCurrentTabScroll(currentCategoryId, mainElement.scrollTop); + if (!ref.current) return; + saveCurrentTabScroll(currentCategoryId, ref.current.scrollTop); }; const [timeoutFn] = useTimeout(handleScroll, 300); useEffect(() => { - const mainElement = document.getElementById('main'); - if (!mainElement) return; + if (!ref.current) return; - mainElement.addEventListener('scroll', timeoutFn); + ref.current.addEventListener('scroll', timeoutFn); return () => { - mainElement.removeEventListener('scroll', timeoutFn); + ref.current?.removeEventListener('scroll', timeoutFn); }; }, [currentCategoryId]); }; diff --git a/frontend/src/pages/ProductListPage.tsx b/frontend/src/pages/ProductListPage.tsx index f494c6d93..e6bd74764 100644 --- a/frontend/src/pages/ProductListPage.tsx +++ b/frontend/src/pages/ProductListPage.tsx @@ -1,6 +1,6 @@ import { BottomSheet, Spacing, useBottomSheet } from '@fun-eat/design-system'; import { useQueryErrorResetBoundary } from '@tanstack/react-query'; -import { Suspense, useEffect } from 'react'; +import { Suspense, useEffect, useRef } from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; @@ -17,7 +17,7 @@ import { import { ProductList } from '@/components/Product'; import { PRODUCT_SORT_OPTIONS } from '@/constants'; import { PATH } from '@/constants/path'; -import { useSortOption } from '@/hooks/common'; +import { useScrollRestoration, useSortOption } from '@/hooks/common'; import { useCategoryContext } from '@/hooks/context'; import { isCategoryVariant } from '@/types/common'; @@ -37,37 +37,36 @@ const ProductListPage = () => { const { categoryIds, currentTabScroll } = useCategoryContext(); const currentCategoryId = categoryIds[category]; - useEffect(() => { - const mainElement = document.getElementById('main'); - if (!mainElement) return; + const productListRef = useRef<HTMLDivElement>(null); + useScrollRestoration(currentCategoryId, productListRef); + useEffect(() => { const scrollY = currentTabScroll[currentCategoryId]; - mainElement.scrollTo(0, scrollY); + productListRef.current?.scrollTo(0, scrollY); }, [currentCategoryId]); return ( <> - <section> - <div style={{ position: 'fixed', background: 'white', top: '60px', width: 'calc(100% - 40px)' }}> - <Title - headingTitle={PAGE_TITLE[category]} - routeDestination={PATH.PRODUCT_LIST + '/' + (category === 'store' ? 'food' : 'store')} - /> - <Spacing size={30} /> - <Suspense fallback={null}> - <CategoryMenu menuVariant={category} /> + <ProductListSection> + <Title + headingTitle={PAGE_TITLE[category]} + routeDestination={PATH.PRODUCT_LIST + '/' + (category === 'store' ? 'food' : 'store')} + /> + <Spacing size={30} /> + <Suspense fallback={null}> + <CategoryMenu menuVariant={category} /> + </Suspense> + <ErrorBoundary fallback={ErrorComponent} handleReset={reset}> + <Suspense fallback={<Loading />}> <SortButtonWrapper> <SortButton option={selectedOption} onClick={handleOpenBottomSheet} /> </SortButtonWrapper> - </Suspense> - </div> - <Spacing size={140} /> - <ErrorBoundary fallback={ErrorComponent} handleReset={reset}> - <Suspense fallback={<Loading />}> - <ProductList category={category} selectedOption={selectedOption} /> + <ProductListWrapper ref={productListRef}> + <ProductList category={category} selectedOption={selectedOption} /> + </ProductListWrapper> </Suspense> </ErrorBoundary> - </section> + </ProductListSection> <ScrollButton /> <BottomSheet ref={ref} isClosing={isClosing} maxWidth="600px" close={handleCloseBottomSheet}> <SortOptionList @@ -82,8 +81,17 @@ const ProductListPage = () => { }; export default ProductListPage; +const ProductListSection = styled.section` + height: 100%; +`; + const SortButtonWrapper = styled.div` display: flex; justify-content: flex-end; margin-top: 20px; `; + +const ProductListWrapper = styled.div` + height: calc(100% - 150px); + overflow-y: auto; +`; From a80dfed1ea16fd31570e4309929371bb3f2e23c0 Mon Sep 17 00:00:00 2001 From: TaeeunKim <xodms0309@naver.com> Date: Thu, 14 Sep 2023 14:14:10 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor:=20CategoryContext=EB=A5=BC=20Ac?= =?UTF-8?q?tion=EA=B3=BC=20Value=20Context=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/CategoryMenu/CategoryMenu.tsx | 5 ++-- .../Product/PBProductList/PBProductList.tsx | 4 +-- .../Product/ProductList/ProductList.tsx | 4 +-- frontend/src/contexts/CategoryContext.tsx | 26 ++++++++++++------- .../src/hooks/common/useScrollRestoration.ts | 4 +-- frontend/src/hooks/context/index.ts | 3 ++- .../hooks/context/useCategoryActionContext.ts | 14 ++++++++++ .../src/hooks/context/useCategoryContext.ts | 11 -------- .../hooks/context/useCategoryValueContext.ts | 14 ++++++++++ frontend/src/pages/ProductListPage.tsx | 4 +-- 10 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 frontend/src/hooks/context/useCategoryActionContext.ts delete mode 100644 frontend/src/hooks/context/useCategoryContext.ts create mode 100644 frontend/src/hooks/context/useCategoryValueContext.ts diff --git a/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx b/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx index 3eb1f41df..2cb59148f 100644 --- a/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx +++ b/frontend/src/components/Common/CategoryMenu/CategoryMenu.tsx @@ -2,7 +2,7 @@ import { Button, theme } from '@fun-eat/design-system'; import type { CSSProp } from 'styled-components'; import styled from 'styled-components'; -import { useCategoryContext } from '@/hooks/context'; +import { useCategoryValueContext, useCategoryActionContext } from '@/hooks/context'; import { useCategoryQuery } from '@/hooks/queries/product'; import type { CategoryVariant } from '@/types/common'; @@ -12,7 +12,8 @@ interface CategoryMenuProps { const CategoryMenu = ({ menuVariant }: CategoryMenuProps) => { const { data: categories } = useCategoryQuery(menuVariant); - const { categoryIds, selectCategory } = useCategoryContext(); + const { categoryIds } = useCategoryValueContext(); + const { selectCategory } = useCategoryActionContext(); const currentCategoryId = categoryIds[menuVariant]; return ( diff --git a/frontend/src/components/Product/PBProductList/PBProductList.tsx b/frontend/src/components/Product/PBProductList/PBProductList.tsx index 105415ff7..087fdbed5 100644 --- a/frontend/src/components/Product/PBProductList/PBProductList.tsx +++ b/frontend/src/components/Product/PBProductList/PBProductList.tsx @@ -6,7 +6,7 @@ import PBProductItem from '../PBProductItem/PBProductItem'; import { MoreButton } from '@/components/Common'; import { PATH } from '@/constants/path'; -import { useCategoryContext } from '@/hooks/context'; +import { useCategoryValueContext } from '@/hooks/context'; import { useInfiniteProductsQuery } from '@/hooks/queries/product'; import displaySlice from '@/utils/displaySlice'; @@ -15,7 +15,7 @@ interface PBProductListProps { } const PBProductList = ({ isHomePage }: PBProductListProps) => { - const { categoryIds } = useCategoryContext(); + const { categoryIds } = useCategoryValueContext(); const { data: pbProductListResponse } = useInfiniteProductsQuery(categoryIds.store); const pbProducts = pbProductListResponse.pages.flatMap((page) => page.products); diff --git a/frontend/src/components/Product/ProductList/ProductList.tsx b/frontend/src/components/Product/ProductList/ProductList.tsx index 86d22cd8a..2afa5f995 100644 --- a/frontend/src/components/Product/ProductList/ProductList.tsx +++ b/frontend/src/components/Product/ProductList/ProductList.tsx @@ -7,7 +7,7 @@ import ProductItem from '../ProductItem/ProductItem'; import { PATH } from '@/constants/path'; import { useIntersectionObserver, useScrollRestoration } from '@/hooks/common'; -import { useCategoryContext } from '@/hooks/context'; +import { useCategoryValueContext } from '@/hooks/context'; import { useInfiniteProductsQuery } from '@/hooks/queries/product'; import type { CategoryVariant, SortOption } from '@/types/common'; import displaySlice from '@/utils/displaySlice'; @@ -21,7 +21,7 @@ interface ProductListProps { const ProductList = ({ category, isHomePage, selectedOption }: ProductListProps) => { const scrollRef = useRef<HTMLDivElement>(null); - const { categoryIds } = useCategoryContext(); + const { categoryIds } = useCategoryValueContext(); const { fetchNextPage, hasNextPage, data } = useInfiniteProductsQuery( categoryIds[category], diff --git a/frontend/src/contexts/CategoryContext.tsx b/frontend/src/contexts/CategoryContext.tsx index 00065b18c..46e008a97 100644 --- a/frontend/src/contexts/CategoryContext.tsx +++ b/frontend/src/contexts/CategoryContext.tsx @@ -12,19 +12,18 @@ type CategoryIds = { [k in CategoryVariant]: number; }; -interface CategoryState { +interface CategoryValue { categoryIds: CategoryIds; - selectCategory: (menuVariant: string, categoryId: number) => void; currentTabScroll: { [key: number]: number }; +} + +interface CategoryAction { + selectCategory: (menuVariant: string, categoryId: number) => void; saveCurrentTabScroll: (categoryId: number, scrollY: number) => void; } -export const CategoryContext = createContext<CategoryState>({ - categoryIds: initialState, - selectCategory: () => {}, - currentTabScroll: {}, - saveCurrentTabScroll: () => {}, -}); +export const CategoryValueContext = createContext<CategoryValue | null>(null); +export const CategoryActionContext = createContext<CategoryAction | null>(null); const CategoryProvider = ({ children }: PropsWithChildren) => { const [categoryIds, setCategoryIds] = useState(initialState); @@ -38,14 +37,21 @@ const CategoryProvider = ({ children }: PropsWithChildren) => { setCurrentTabScroll((prevState) => ({ ...prevState, [categoryId]: scrollY })); }; - const categoryState: CategoryState = { + const categoryValue = { categoryIds, currentTabScroll, + }; + + const categoryAction = { selectCategory, saveCurrentTabScroll, }; - return <CategoryContext.Provider value={categoryState}>{children}</CategoryContext.Provider>; + return ( + <CategoryActionContext.Provider value={categoryAction}> + <CategoryValueContext.Provider value={categoryValue}>{children}</CategoryValueContext.Provider> + </CategoryActionContext.Provider> + ); }; export default CategoryProvider; diff --git a/frontend/src/hooks/common/useScrollRestoration.ts b/frontend/src/hooks/common/useScrollRestoration.ts index 75e1a68e3..c03001048 100644 --- a/frontend/src/hooks/common/useScrollRestoration.ts +++ b/frontend/src/hooks/common/useScrollRestoration.ts @@ -2,10 +2,10 @@ import type { RefObject } from 'react'; import { useEffect } from 'react'; import useTimeout from './useTimeout'; -import { useCategoryContext } from '../context'; +import { useCategoryActionContext } from '../context'; const useScrollRestoration = (currentCategoryId: number, ref: RefObject<HTMLElement>) => { - const { saveCurrentTabScroll } = useCategoryContext(); + const { saveCurrentTabScroll } = useCategoryActionContext(); const handleScroll = () => { if (!ref.current) return; diff --git a/frontend/src/hooks/context/index.ts b/frontend/src/hooks/context/index.ts index a0a01b373..56470cfbb 100644 --- a/frontend/src/hooks/context/index.ts +++ b/frontend/src/hooks/context/index.ts @@ -1,4 +1,5 @@ -export { default as useCategoryContext } from './useCategoryContext'; +export { default as useCategoryValueContext } from './useCategoryValueContext'; +export { default as useCategoryActionContext } from './useCategoryActionContext'; export { default as useReviewFormActionContext } from './useReviewFormActionContext'; export { default as useReviewFormValueContext } from './useReviewFormValueContext'; export { default as useRecipeFormActionContext } from './useRecipeFormActionContext'; diff --git a/frontend/src/hooks/context/useCategoryActionContext.ts b/frontend/src/hooks/context/useCategoryActionContext.ts new file mode 100644 index 000000000..bc353818d --- /dev/null +++ b/frontend/src/hooks/context/useCategoryActionContext.ts @@ -0,0 +1,14 @@ +import { useContext } from 'react'; + +import { CategoryActionContext } from '@/contexts/CategoryContext'; + +const useCategoryActionContext = () => { + const categoryAction = useContext(CategoryActionContext); + if (categoryAction === null || categoryAction === undefined) { + throw new Error('useCategoryActionContext는 Category Provider 안에서 사용해야 합니다.'); + } + + return categoryAction; +}; + +export default useCategoryActionContext; diff --git a/frontend/src/hooks/context/useCategoryContext.ts b/frontend/src/hooks/context/useCategoryContext.ts deleted file mode 100644 index 9f7a676f8..000000000 --- a/frontend/src/hooks/context/useCategoryContext.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useContext } from 'react'; - -import { CategoryContext } from '@/contexts/CategoryContext'; - -const useCategoryContext = () => { - const { categoryIds, selectCategory, currentTabScroll, saveCurrentTabScroll } = useContext(CategoryContext); - - return { categoryIds, selectCategory, currentTabScroll, saveCurrentTabScroll }; -}; - -export default useCategoryContext; diff --git a/frontend/src/hooks/context/useCategoryValueContext.ts b/frontend/src/hooks/context/useCategoryValueContext.ts new file mode 100644 index 000000000..2f159e2dd --- /dev/null +++ b/frontend/src/hooks/context/useCategoryValueContext.ts @@ -0,0 +1,14 @@ +import { useContext } from 'react'; + +import { CategoryValueContext } from '@/contexts/CategoryContext'; + +const useCategoryValueContext = () => { + const categoryValue = useContext(CategoryValueContext); + if (categoryValue === null || categoryValue === undefined) { + throw new Error('useCategoryValueContext는 Category Provider 안에서 사용해야 합니다.'); + } + + return categoryValue; +}; + +export default useCategoryValueContext; diff --git a/frontend/src/pages/ProductListPage.tsx b/frontend/src/pages/ProductListPage.tsx index e6bd74764..075a42459 100644 --- a/frontend/src/pages/ProductListPage.tsx +++ b/frontend/src/pages/ProductListPage.tsx @@ -18,7 +18,7 @@ import { ProductList } from '@/components/Product'; import { PRODUCT_SORT_OPTIONS } from '@/constants'; import { PATH } from '@/constants/path'; import { useScrollRestoration, useSortOption } from '@/hooks/common'; -import { useCategoryContext } from '@/hooks/context'; +import { useCategoryValueContext } from '@/hooks/context'; import { isCategoryVariant } from '@/types/common'; const PAGE_TITLE = { food: '공통 상품', store: 'PB 상품' }; @@ -34,7 +34,7 @@ const ProductListPage = () => { const { selectedOption, selectSortOption } = useSortOption(PRODUCT_SORT_OPTIONS[0]); const { reset } = useQueryErrorResetBoundary(); - const { categoryIds, currentTabScroll } = useCategoryContext(); + const { categoryIds, currentTabScroll } = useCategoryValueContext(); const currentCategoryId = categoryIds[category]; const productListRef = useRef<HTMLDivElement>(null); From fe604427c2a266190ffb545286726ae1d6377563 Mon Sep 17 00:00:00 2001 From: TaeeunKim <xodms0309@naver.com> Date: Thu, 14 Sep 2023 15:58:14 +0900 Subject: [PATCH 09/11] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=81=AC=EB=A1=A4?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=EC=9D=84=20ProductList=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Product/ProductList/ProductList.tsx | 22 +++++++++---- .../src/hooks/common/useScrollRestoration.ts | 6 +++- frontend/src/pages/ProductListPage.tsx | 31 ++++--------------- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/frontend/src/components/Product/ProductList/ProductList.tsx b/frontend/src/components/Product/ProductList/ProductList.tsx index 2afa5f995..40b492f73 100644 --- a/frontend/src/components/Product/ProductList/ProductList.tsx +++ b/frontend/src/components/Product/ProductList/ProductList.tsx @@ -20,6 +20,7 @@ interface ProductListProps { const ProductList = ({ category, isHomePage, selectedOption }: ProductListProps) => { const scrollRef = useRef<HTMLDivElement>(null); + const productListRef = useRef<HTMLDivElement>(null); const { categoryIds } = useCategoryValueContext(); @@ -27,13 +28,17 @@ const ProductList = ({ category, isHomePage, selectedOption }: ProductListProps) categoryIds[category], selectedOption?.value ?? 'reviewCount,desc' ); + + useIntersectionObserver<HTMLDivElement>(fetchNextPage, scrollRef, hasNextPage); + + useScrollRestoration(categoryIds[category], productListRef); + const productList = data.pages.flatMap((page) => page.products); const productsToDisplay = displaySlice(isHomePage, productList); - useIntersectionObserver<HTMLDivElement>(fetchNextPage, scrollRef, hasNextPage); return ( - <> - <ProductListContainer> + <ProductListContainer ref={productListRef}> + <ProductListWrapper> {productsToDisplay.map((product) => ( <li key={product.id}> <Link as={RouterLink} to={`${PATH.PRODUCT_LIST}/${category}/${product.id}`}> @@ -41,14 +46,19 @@ const ProductList = ({ category, isHomePage, selectedOption }: ProductListProps) </Link> </li> ))} - </ProductListContainer> + </ProductListWrapper> <div ref={scrollRef} aria-hidden /> - </> + </ProductListContainer> ); }; export default ProductList; -const ProductListContainer = styled.ul` +const ProductListContainer = styled.div` + height: calc(100% - 150px); + overflow-y: auto; +`; + +const ProductListWrapper = styled.ul` display: flex; flex-direction: column; diff --git a/frontend/src/hooks/common/useScrollRestoration.ts b/frontend/src/hooks/common/useScrollRestoration.ts index c03001048..00e53598a 100644 --- a/frontend/src/hooks/common/useScrollRestoration.ts +++ b/frontend/src/hooks/common/useScrollRestoration.ts @@ -2,10 +2,11 @@ import type { RefObject } from 'react'; import { useEffect } from 'react'; import useTimeout from './useTimeout'; -import { useCategoryActionContext } from '../context'; +import { useCategoryActionContext, useCategoryValueContext } from '../context'; const useScrollRestoration = (currentCategoryId: number, ref: RefObject<HTMLElement>) => { const { saveCurrentTabScroll } = useCategoryActionContext(); + const { currentTabScroll } = useCategoryValueContext(); const handleScroll = () => { if (!ref.current) return; @@ -19,6 +20,9 @@ const useScrollRestoration = (currentCategoryId: number, ref: RefObject<HTMLElem ref.current.addEventListener('scroll', timeoutFn); + const scrollY = currentTabScroll[currentCategoryId]; + ref.current.scrollTo(0, scrollY); + return () => { ref.current?.removeEventListener('scroll', timeoutFn); }; diff --git a/frontend/src/pages/ProductListPage.tsx b/frontend/src/pages/ProductListPage.tsx index 075a42459..ae32a6b29 100644 --- a/frontend/src/pages/ProductListPage.tsx +++ b/frontend/src/pages/ProductListPage.tsx @@ -1,6 +1,6 @@ import { BottomSheet, Spacing, useBottomSheet } from '@fun-eat/design-system'; import { useQueryErrorResetBoundary } from '@tanstack/react-query'; -import { Suspense, useEffect, useRef } from 'react'; +import { Suspense } from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; @@ -17,8 +17,7 @@ import { import { ProductList } from '@/components/Product'; import { PRODUCT_SORT_OPTIONS } from '@/constants'; import { PATH } from '@/constants/path'; -import { useScrollRestoration, useSortOption } from '@/hooks/common'; -import { useCategoryValueContext } from '@/hooks/context'; +import { useSortOption } from '@/hooks/common'; import { isCategoryVariant } from '@/types/common'; const PAGE_TITLE = { food: '공통 상품', store: 'PB 상품' }; @@ -26,24 +25,13 @@ const PAGE_TITLE = { food: '공통 상품', store: 'PB 상품' }; const ProductListPage = () => { const { category } = useParams(); - if (!category || !isCategoryVariant(category)) { - return null; - } - const { ref, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet(); const { selectedOption, selectSortOption } = useSortOption(PRODUCT_SORT_OPTIONS[0]); const { reset } = useQueryErrorResetBoundary(); - const { categoryIds, currentTabScroll } = useCategoryValueContext(); - const currentCategoryId = categoryIds[category]; - - const productListRef = useRef<HTMLDivElement>(null); - useScrollRestoration(currentCategoryId, productListRef); - - useEffect(() => { - const scrollY = currentTabScroll[currentCategoryId]; - productListRef.current?.scrollTo(0, scrollY); - }, [currentCategoryId]); + if (!category || !isCategoryVariant(category)) { + return null; + } return ( <> @@ -61,9 +49,7 @@ const ProductListPage = () => { <SortButtonWrapper> <SortButton option={selectedOption} onClick={handleOpenBottomSheet} /> </SortButtonWrapper> - <ProductListWrapper ref={productListRef}> - <ProductList category={category} selectedOption={selectedOption} /> - </ProductListWrapper> + <ProductList category={category} selectedOption={selectedOption} /> </Suspense> </ErrorBoundary> </ProductListSection> @@ -90,8 +76,3 @@ const SortButtonWrapper = styled.div` justify-content: flex-end; margin-top: 20px; `; - -const ProductListWrapper = styled.div` - height: calc(100% - 150px); - overflow-y: auto; -`; From ad80ff811948c6ac02e50848bdda1bcdba8eafaf Mon Sep 17 00:00:00 2001 From: TaeeunKim <xodms0309@naver.com> Date: Thu, 14 Sep 2023 15:59:22 +0900 Subject: [PATCH 10/11] =?UTF-8?q?style:=20=EC=A4=91=EA=B4=84=ED=98=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/common/useScrollRestoration.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/common/useScrollRestoration.ts b/frontend/src/hooks/common/useScrollRestoration.ts index 00e53598a..6f3d00bf9 100644 --- a/frontend/src/hooks/common/useScrollRestoration.ts +++ b/frontend/src/hooks/common/useScrollRestoration.ts @@ -9,14 +9,18 @@ const useScrollRestoration = (currentCategoryId: number, ref: RefObject<HTMLElem const { currentTabScroll } = useCategoryValueContext(); const handleScroll = () => { - if (!ref.current) return; + if (!ref.current) { + return; + } saveCurrentTabScroll(currentCategoryId, ref.current.scrollTop); }; const [timeoutFn] = useTimeout(handleScroll, 300); useEffect(() => { - if (!ref.current) return; + if (!ref.current) { + return; + } ref.current.addEventListener('scroll', timeoutFn); From 5b5942ac63681bcf110515b262220198a6df49c5 Mon Sep 17 00:00:00 2001 From: TaeeunKim <xodms0309@naver.com> Date: Thu, 14 Sep 2023 16:45:29 +0900 Subject: [PATCH 11/11] =?UTF-8?q?feat:=20CategoryProvider=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=90=EC=8B=B8=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/router/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index 6184ae187..7bf9565ea 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -23,7 +23,11 @@ import SearchPage from '@/pages/SearchPage'; const router = createBrowserRouter([ { path: '/', - element: <App />, + element: ( + <CategoryProvider> + <App /> + </CategoryProvider> + ), errorElement: <NotFoundPage />, children: [ {