Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] fix: 리뷰 작성 시 리뷰 탭 이동 및 유지 #623

Merged
merged 13 commits into from
Sep 14, 2023
30 changes: 30 additions & 0 deletions frontend/__tests__/hooks/useTabMenu.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useTabMenu } from '@/hooks/common';
import { renderHook, act } from '@testing-library/react';

it('선택된 탭 초기 상태는 0번 인덱스이다.', () => {
const { result } = renderHook(() => useTabMenu());

expect(result.current.selectedTabMenu).toBe(0);
expect(result.current.isFirstTabMenu).toBe(true);
});

it('handleTabMenuClick를 사용하여 선택한 탭 인덱스를 저장할 수 있다. ', () => {
const { result } = renderHook(() => useTabMenu());

act(() => {
result.current.handleTabMenuClick(1);
});

expect(result.current.selectedTabMenu).toBe(1);
});

it('initTabMenu를 사용하여 선택된 탭을 맨 처음 탭으로 초기화할 수 있다.', () => {
const { result } = renderHook(() => useTabMenu());

act(() => {
result.current.handleTabMenuClick(1);
result.current.initTabMenu();
});

expect(result.current.selectedTabMenu).toBe(0);
});
19 changes: 14 additions & 5 deletions frontend/src/components/Common/TabMenu/TabMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,26 @@ import styled from 'styled-components';

interface TabMenuProps {
tabMenus: readonly string[];
selectedTabMenu: string;
handleTabMenuSelect: MouseEventHandler<HTMLButtonElement>;
selectedTabMenu: number;
handleTabMenuSelect: (index: number) => void;
}

const TabMenu = (
{ tabMenus, selectedTabMenu, handleTabMenuSelect }: TabMenuProps,
ref: ForwardedRef<HTMLUListElement>
) => {
const handleTabMenuClick: MouseEventHandler<HTMLButtonElement> = (event) => {
const { index } = event.currentTarget.dataset;

if (index) {
handleTabMenuSelect(Number(index));
}
};

return (
<TabMenuContainer ref={ref}>
{tabMenus.map((menu) => {
const isSelected = selectedTabMenu === menu;
{tabMenus.map((menu, index) => {
const isSelected = selectedTabMenu === index;
return (
<TabMenuItem key={menu} isSelected={isSelected}>
<TabMenuButton
Expand All @@ -27,7 +35,8 @@ const TabMenu = (
weight={isSelected ? 'bold' : 'regular'}
variant="transparent"
value={menu}
onClick={handleTabMenuSelect}
onClick={handleTabMenuClick}
data-index={index}
>
{menu}
</TabMenuButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ interface ReviewRegisterFormProps {
productId: number;
targetRef: RefObject<HTMLElement>;
closeReviewDialog: () => void;
initTabMenu: () => void;
}

const ReviewRegisterForm = ({ productId, targetRef, closeReviewDialog }: ReviewRegisterFormProps) => {
const ReviewRegisterForm = ({ productId, targetRef, closeReviewDialog, initTabMenu }: ReviewRegisterFormProps) => {
const { scrollToPosition } = useScroll();
const { previewImage, imageFile, uploadImage, deleteImage } = useImageUploader();

Expand Down Expand Up @@ -61,6 +62,7 @@ const ReviewRegisterForm = ({ productId, targetRef, closeReviewDialog }: ReviewR
mutate(formData, {
onSuccess: () => {
resetAndCloseForm();
initTabMenu();
scrollToPosition(targetRef);
},
onError: (error) => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 useTabMenu } from './useTabMenu';
26 changes: 26 additions & 0 deletions frontend/src/hooks/common/useTabMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useState } from 'react';

const INIT_TAB_INDEX = 0;

const useTabMenu = () => {
const [selectedTabMenu, setSelectedTabMenu] = useState(INIT_TAB_INDEX);

const isFirstTabMenu = selectedTabMenu === INIT_TAB_INDEX;

const handleTabMenuClick = (index: number) => {
setSelectedTabMenu(index);
};

const initTabMenu = () => {
setSelectedTabMenu(INIT_TAB_INDEX);
};

return {
selectedTabMenu,
isFirstTabMenu,
handleTabMenuClick,
initTabMenu,
};
};

export default useTabMenu;
11 changes: 5 additions & 6 deletions frontend/src/pages/ProductDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { BottomSheet, Spacing, useBottomSheet, Text, Link } from '@fun-eat/design-system';
import { useQueryErrorResetBoundary } from '@tanstack/react-query';
import type { MouseEventHandler } from 'react';
import { useState, useRef, Suspense } from 'react';
import ReactGA from 'react-ga4';
import { useParams, Link as RouterLink } from 'react-router-dom';
Expand All @@ -22,7 +21,7 @@ import { ReviewList, ReviewRegisterForm } from '@/components/Review';
import { RECIPE_SORT_OPTIONS, REVIEW_SORT_OPTIONS } from '@/constants';
import { PATH } from '@/constants/path';
import ReviewFormProvider from '@/contexts/ReviewFormContext';
import { useSortOption } from '@/hooks/common';
import { useSortOption, useTabMenu } from '@/hooks/common';
import { useMemberQuery } from '@/hooks/queries/members';
import { useProductDetailQuery } from '@/hooks/queries/product';

Expand All @@ -38,10 +37,9 @@ const ProductDetailPage = () => {
const { reset } = useQueryErrorResetBoundary();

const tabMenus = [`리뷰 ${productDetail.reviewCount}`, '꿀조합'];
const [selectedTabMenu, setSelectedTabMenu] = useState(tabMenus[0]);
const { selectedTabMenu, isFirstTabMenu: isReviewTab, handleTabMenuClick, initTabMenu } = useTabMenu();
const tabRef = useRef<HTMLUListElement>(null);

const isReviewTab = selectedTabMenu === tabMenus[0];
const sortOptions = isReviewTab ? REVIEW_SORT_OPTIONS : RECIPE_SORT_OPTIONS;
const initialSortOption = isReviewTab ? REVIEW_SORT_OPTIONS[0] : RECIPE_SORT_OPTIONS[0];

Expand All @@ -64,8 +62,8 @@ const ProductDetailPage = () => {
handleOpenBottomSheet();
};

const handleTabMenuSelect: MouseEventHandler<HTMLButtonElement> = (event) => {
setSelectedTabMenu(event.currentTarget.value);
const handleTabMenuSelect = (index: number) => {
handleTabMenuClick(index);
selectSortOption(initialSortOption);

ReactGA.event({
Expand Down Expand Up @@ -132,6 +130,7 @@ const ProductDetailPage = () => {
targetRef={tabRef}
productId={Number(productId)}
closeReviewDialog={handleCloseBottomSheet}
initTabMenu={initTabMenu}
/>
</ReviewFormProvider>
) : (
Expand Down
26 changes: 9 additions & 17 deletions frontend/src/pages/SearchPage.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { Button, Heading, Spacing, Text } from '@fun-eat/design-system';
import { useQueryErrorResetBoundary } from '@tanstack/react-query';
import type { MouseEventHandler } from 'react';
import { Suspense, useEffect, useState } from 'react';
import styled from 'styled-components';

import { ErrorBoundary, ErrorComponent, Input, Loading, SvgIcon, TabMenu } from '@/components/Common';
import { RecommendList, ProductSearchResultList, RecipeSearchResultList } from '@/components/Search';
import { SEARCH_PAGE_TABS } from '@/constants';
import { useDebounce } from '@/hooks/common';
import { useDebounce, useTabMenu } from '@/hooks/common';
import { useSearch } from '@/hooks/search';

const isProductSearchTab = (tabMenu: string) => tabMenu === SEARCH_PAGE_TABS[0];
const getInputPlaceholder = (tabMenu: string) =>
isProductSearchTab(tabMenu) ? '상품 이름을 검색해보세요.' : '꿀조합에 포함된 상품을 입력해보세요.';
const PRODUCT_PLACEHOLDER = '상품 이름을 검색해보세요.';
const RECIPE_PLACEHOLDER = '꿀조합에 포함된 상품을 입력해보세요.';

const SearchPage = () => {
const {
Expand All @@ -26,12 +24,10 @@ const SearchPage = () => {
handleAutocompleteClose,
} = useSearch();
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery || '');
const [selectedTabMenu, setSelectedTabMenu] = useState<string>(SEARCH_PAGE_TABS[0]);
const { reset } = useQueryErrorResetBoundary();

const handleTabMenuSelect: MouseEventHandler<HTMLButtonElement> = (event) => {
setSelectedTabMenu(event.currentTarget.value);
};
const { selectedTabMenu, isFirstTabMenu: isProductSearchTab, handleTabMenuClick } = useTabMenu();
const inputPlaceholder = isProductSearchTab ? PRODUCT_PLACEHOLDER : RECIPE_PLACEHOLDER;

useDebounce(
() => {
Expand All @@ -53,7 +49,7 @@ const SearchPage = () => {
<form onSubmit={handleSearch}>
<Input
customWidth="100%"
placeholder={getInputPlaceholder(selectedTabMenu)}
placeholder={inputPlaceholder}
rightIcon={
<Button customHeight="36px" color="white">
<SvgIcon variant="search" />
Expand All @@ -77,11 +73,7 @@ const SearchPage = () => {
)}
</SearchSection>
<Spacing size={20} />
<TabMenu
tabMenus={SEARCH_PAGE_TABS}
selectedTabMenu={selectedTabMenu}
handleTabMenuSelect={handleTabMenuSelect}
/>
<TabMenu tabMenus={SEARCH_PAGE_TABS} selectedTabMenu={selectedTabMenu} handleTabMenuSelect={handleTabMenuClick} />
<SearchResultSection>
{isSubmitted && debouncedSearchQuery ? (
<>
Expand All @@ -91,7 +83,7 @@ const SearchPage = () => {
<ErrorBoundary fallback={ErrorComponent}>
<Suspense fallback={<Loading />}>
<Spacing size={20} />
{isProductSearchTab(selectedTabMenu) ? (
{isProductSearchTab ? (
<ProductSearchResultList searchQuery={debouncedSearchQuery} />
) : (
<RecipeSearchResultList searchQuery={debouncedSearchQuery} />
Expand All @@ -100,7 +92,7 @@ const SearchPage = () => {
</ErrorBoundary>
</>
) : (
<Text>{selectedTabMenu}을 검색해보세요.</Text>
<Text>{SEARCH_PAGE_TABS[selectedTabMenu]}을 검색해보세요.</Text>
)}
</SearchResultSection>
</>
Expand Down