Skip to content

Commit

Permalink
refactor(toks-main): 카테고리 기능 리팩토링 및 Bug Fixed (#402)
Browse files Browse the repository at this point in the history
* refactor: 카테고리 기능 리팩토링 및 Bug Fixed

* fix: order lint fix

* fix: template.tsx에서 RecoilRoot 리렌더링되어 상태 초기화되는 이슈 fixed

* fix: category 가나다라 순으로 프론트에서 처리

* fix: image url public 안들어가는 이슈 fix

* fix: category flow 주석 추가
  • Loading branch information
minsgy authored Feb 15, 2024
1 parent 6dbc8c8 commit 132cbf1
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 112 deletions.
11 changes: 11 additions & 0 deletions public/img/icon/question.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/app/_context/Provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client';

import React from 'react';
import { RecoilRoot } from 'recoil';

const Provider = ({ children }: StrictPropsWithChildren) => {
return <RecoilRoot>{children}</RecoilRoot>;
};

export default Provider;
6 changes: 5 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { bgColor } from '@/common/foundation';
import QueryProvider from '@/common/providers/QueryProvider';
import * as gtag from '@/common/utils';

import Provider from './_context/Provider';

export const metadata: Metadata = {
viewport: {
width: 'device-width',
Expand Down Expand Up @@ -73,7 +75,9 @@ export default function RootLayout({
/>
<body className={clsx(pretendard.className, bgColor['mainLayout'])}>
<QueryProvider>
<StyledLayout>{children}</StyledLayout>
<Provider>
<StyledLayout>{children}</StyledLayout>
</Provider>
</QueryProvider>
</body>
</html>
Expand Down
6 changes: 3 additions & 3 deletions src/app/quiz/components/Comment/LikeButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { useUnlikeCommentMutation } from '@/app/quiz/hooks/useUnlikeCommentMutat
import { Text, useAuth } from '@/common';
import { LOGIN_URL } from '@/common/constants';

import like_off from '../../../../../public/img/icon/like_off.svg';
import like_on from '../../../../../public/img/icon/like_on.svg';
import likeOff from '../../../../../public/img/icon/like_off.svg';
import likeOn from '../../../../../public/img/icon/like_on.svg';

interface LikeButtonProps
extends Omit<HTMLAttributes<HTMLSpanElement>, 'className'> {
Expand Down Expand Up @@ -46,7 +46,7 @@ function LikeButton({
}}
>
<Image
src={isLiked ? like_on : like_off}
src={isLiked ? likeOn : likeOff}
alt="좋아요 버튼"
width={18}
height={18}
Expand Down
31 changes: 14 additions & 17 deletions src/app/template.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';

import { usePathname } from 'next/navigation';
import { RecoilRoot } from 'recoil';

import { Appbar, BackHeader, GlobalPortal } from '@/common';

Expand All @@ -13,21 +12,19 @@ export default function Template({ children }: StrictPropsWithChildren) {
const pathName = usePathname();

return (
<RecoilRoot>
<GlobalPortal.Provider>
{pathName === '/toks-main' ? (
<>
<Appbar />
{children}
<CategoryBottomSheet />
</>
) : (
<>
<BackHeader />
{children}
</>
)}
</GlobalPortal.Provider>
</RecoilRoot>
<GlobalPortal.Provider>
{pathName === '/toks-main' ? (
<>
<Appbar />
{children}
<CategoryBottomSheet />
</>
) : (
<>
<BackHeader />
{children}
</>
)}
</GlobalPortal.Provider>
);
}
56 changes: 42 additions & 14 deletions src/app/toks-main/components/CategoryBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';

import Image from 'next/image';
import React, { useState } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { useRecoilState } from 'recoil';

import { BottomSheet, Button, ICON_URL, Tab, Text } from '@/common';
import { useAuth, usePreventScroll } from '@/common/hooks';
Expand All @@ -16,27 +16,55 @@ import { CategoryButtonGroups } from './CategoryButtonGroups';
import { useCategoryUpdateMutation } from '../hooks/useCategoryUpdateMutation';

export const CategoryBottomSheet = () => {
const { isLogin } = useAuth();
const [isShow, setIsShow] = useRecoilState(isVisibleCategoryBottomSheetAtom);
const { mutate: updateCategories } = useCategoryUpdateMutation();
const { data: selectedCategory = [] } = useSelectedCategoriesQuery();
const { isLogin } = useAuth();

const { data: selectedLoginCategory = [] } = useSelectedCategoriesQuery();
const [selectedLocalCategory, setSelectedLocalCategories] = useState<
string[]
>(selectedCategory ?? []);
>([]);

const [selectedTemporaryCategory, setSelectedTemporaryCategory] =
useRecoilState(selectedTemporaryCategoryAtom);

const setSelectedTemporaryCategory = useSetRecoilState(
selectedTemporaryCategoryAtom
);
const { data: categoryQuery } = useCategoriesQuery();
const [isShow, setIsShow] = useRecoilState(isVisibleCategoryBottomSheetAtom);
const [selectedTab, setSelectedTab] = useState(0);
usePreventScroll(isShow);

const panel = categoryQuery?.panels[selectedTab];
const buttons = panel?.map(({ id, name }) => ({
const [selectedTab, setSelectedTab] = useState(0);
const { data: categoryQuery } = useCategoriesQuery();
const selectedPanel = categoryQuery?.panels[selectedTab];
const buttons = selectedPanel?.map(({ id, name }) => ({
label: name,
value: id,
}));

const tabs = categoryQuery?.tabs.map(({ name }) => name) ?? [];

/**
* useLayoutEffect(동기) -> useEffect(비동기) 순으로 렌더링된다.
* STEP1. BottomSheet가 열릴 때마다 selectedTab, selectedLocalCategories를 초기화한다.
* STEP2. 로그인 상태일 때, 로그인한 사용자의 관심 카테고리를 selectedLocalCategories에 저장한다.
* STEP3. 로그인 상태가 아니고, selectedTemporaryCategory가 존재할 때, selectedLocalCategories에 저장한다.
*
* 이를 통해 BottomSheet가 열릴 때마다 초기화되고, 저장한 카테고리/로그인 정보 카테고리를 불러온다.
*/
useLayoutEffect(() => {
setSelectedTab(0);
setSelectedLocalCategories([]);
}, [isShow]);

useEffect(() => {
if (isLogin && isShow && selectedLoginCategory.length > 0) {
setSelectedLocalCategories(selectedLoginCategory);
}
}, [isLogin, isShow, selectedLoginCategory]);

useEffect(() => {
if (selectedTemporaryCategory.length > 0 && isShow) {
setSelectedLocalCategories(selectedTemporaryCategory);
}
}, [isShow, selectedTemporaryCategory]);

return (
<BottomSheet
className="flex h-categoryBottomSheet flex-col "
Expand All @@ -55,7 +83,7 @@ export const CategoryBottomSheet = () => {
</div>
<Tab
activeIndex={selectedTab}
tabs={categoryQuery?.tabs ?? []}
tabs={tabs}
onTabChange={(index) => {
setSelectedTab(index);
}}
Expand Down
2 changes: 1 addition & 1 deletion src/app/toks-main/components/MainPageBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BottomSheet } from '@/common';
import { BottomSheetProps } from '@/common/components/BottomSheet/types';
import { useAuth, usePreventScroll } from '@/common/hooks';
import { BottomSheetProps } from '@/types/bottomsheet';

import { OnboardingBottomSheet } from './OnboardingBottomsheet';
import { ProgressCheckBottomSheet } from './ProgressCheckBottomSheet';
Expand Down
55 changes: 55 additions & 0 deletions src/app/toks-main/components/QuizCategory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client';

import { getCookie } from 'cookies-next';
import React, { memo } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';

import { Categories } from '@/common/components/Categories';
import { useSelectedCategoriesQuery } from '@/queries';
import { useCategoriesQuery } from '@/queries/useCategoriesQuery';
import {
isVisibleCategoryBottomSheetAtom,
selectedTemporaryCategoryAtom,
} from '@/store';

import { isSameCategory } from '../utils';

const QuizCategory = () => {
const accessToken = getCookie('accessToken');
const { data: categoryQuery } = useCategoriesQuery();
const { data: selectedLoginCategory } = useSelectedCategoriesQuery();
const selectedTemporaryCategory = useRecoilValue(
selectedTemporaryCategoryAtom
);

const setIsOpenCategoryBottomSheet = useSetRecoilState(
isVisibleCategoryBottomSheetAtom
);

const selectedCategory = Boolean(accessToken)
? selectedLoginCategory
: selectedTemporaryCategory;

const categories = categoryQuery?.tabs.map(({ name, id }) => {
const isSelected =
selectedCategory?.filter((categoryId) =>
isSameCategory(categoryId, id)
) ?? [];

return {
categoryName: `${name} ${isSelected.length}`,
isSelected: isSelected.length > 0,
};
});

return (
<Categories
categories={categories ?? []}
onClick={() => {
setIsOpenCategoryBottomSheet(true);
}}
/>
);
};

export default memo(QuizCategory);
3 changes: 3 additions & 0 deletions src/app/toks-main/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const isSameCategory = (a: string, b: string) => {
return a.slice(0, 2) === b.slice(0, 2);
};
77 changes: 12 additions & 65 deletions src/common/components/Appbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,92 +3,38 @@
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';

import { ICON_URL, LOGIN_URL } from '@/common/constants';
import QuizCategory from '@/app/toks-main/components/QuizCategory';
import { GOOGLE_FORM_URL, ICON_URL, LOGIN_URL } from '@/common/constants';
import { useAuth } from '@/common/hooks';
import { useSelectedCategoriesQuery } from '@/queries';
import {
isVisibleCategoryBottomSheetAtom,
isVisibleFloatingButtonBottomSheetAtom,
selectedTemporaryCategoryAtom,
} from '@/store';

import questionSvg from '../../../../public/img/icon/question.svg';
import { SSRSuspense } from '../SSRSuspense';
import { Text } from '../Text';
import { Tooltip } from '../Tooltip';

export const Appbar = () => {
const router = useRouter();
const { isLogin, user } = useAuth();
const [isOpenCategoryBottomSheet, setIsOpenCategoryBottomSheet] =
useRecoilState(isVisibleCategoryBottomSheetAtom);
const isOpenFloatingButtonBottomSheet = useRecoilValue(
isVisibleFloatingButtonBottomSheetAtom
);
const localSelectedCategoryArray = useRecoilValue(
selectedTemporaryCategoryAtom
);
const { data: serverSelectedCategory = [] } = useSelectedCategoriesQuery();

const renderCategoryCountBadge = () => {
if (isLogin && serverSelectedCategory.length > 0) {
return serverSelectedCategory.length;
}
if (!isLogin && localSelectedCategoryArray.length > 0) {
return localSelectedCategoryArray.length;
}

return null;
};

// TODO: useAppbar hook 구현
return (
<SSRSuspense
fallback={<div className="h-54px bg-gray-120">로딩중입니다..</div>}
fallback={<div className="h-64px bg-gray-120">로딩중입니다..</div>}
>
<nav className="sticky left-0 right-0 top-0 z-50 h-54px bg-gray-120">
<div className="flex h-full w-full items-center justify-between">
<div
className="flex items-center gap-4px"
role="button"
onClick={() => {
setIsOpenCategoryBottomSheet(true);
}}
>
<nav className="sticky left-0 right-0 top-0 z-50 bg-gray-120 pb-[20px]">
<div className="flex w-full items-center justify-between pb-[20px] pt-[16px]">
<div className="flex items-center gap-4px" role="button">
<Image
layout="fixed"
width={60}
height={20}
src={ICON_URL.TOKS_LOGO}
alt="toks 로고"
/>
{/* TODO: POPOVER 구현 */}
<Tooltip
isFirstRender
message="관심있는 카테고리를 선택해보세요"
isVisibleTooltip={
!isOpenCategoryBottomSheet && !isOpenFloatingButtonBottomSheet
}
>
<button type="button" className="h-fit">
<Image
src={ICON_URL.CHEVRON_DOWN}
alt="카테고리 드롭다운"
width={24}
height={24}
/>
</button>
</Tooltip>
{renderCategoryCountBadge() && (
<div className="flex h-[26px] w-[26px] items-center justify-center rounded-6px bg-primary-default text-white">
<Text typo="subheadingBold" color="white">
{renderCategoryCountBadge()}
</Text>
</div>
)}
</div>
<button className="flex items-center">
<button className="flex items-center gap-[12px]">
<a href={GOOGLE_FORM_URL} target="_blank" rel="noreferrer">
<Image src={questionSvg} alt="질문하기" width={28} height={28} />
</a>
{/* TODO: 로그인 여부 분기 */}
{isLogin ? (
<div className="relative h-[30px] w-[30px]">
Expand Down Expand Up @@ -116,6 +62,7 @@ export const Appbar = () => {
)}
</button>
</div>
<QuizCategory />
</nav>
</SSRSuspense>
);
Expand Down
6 changes: 0 additions & 6 deletions src/common/components/BottomSheet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import React, { PropsWithChildren } from 'react';

// import { useClickAway } from '@/common/hooks/useClickAway';
import { cn } from '@/common/utils';

import { BottomSheetProps } from './types';
Expand All @@ -14,11 +13,6 @@ export const BottomSheet = ({
onClose,
className,
}: PropsWithChildren<BottomSheetProps>) => {
// const bottomSheetContentRef = useClickAway({
// callback: () => {
// onClose();
// },
// });
return (
<GlobalPortal.Consumer>
<Dimmer isShow={isShow} onClose={() => onClose()} />
Expand Down
Loading

0 comments on commit 132cbf1

Please sign in to comment.