From c8839d0fea0f992966261c8742a198696d01517a Mon Sep 17 00:00:00 2001 From: Sohyun Park <124856726+ParkSohyunee@users.noreply.github.com> Date: Tue, 12 Mar 2024 23:59:46 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Refactor:=20=ED=94=BC=EB=93=9C=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#1?= =?UTF-8?q?93)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Design: 피드페이지 - 카테고리 스켈레톤 UI 수정 * Style: 주석 정리 * Style: 카테고리 컴포넌트 'use client' 제거 * Design: 피드페이지 - 프로필 컴포넌트 스켈레톤 UI 수정 및 isLoading으로 속성 변경 * Refactor: 모달 버튼 공통컴포넌트로 적용, 페이지 이동 Link 컴포넌트로 변경 * Fix: 다국어 기능 팔로우 -> 팔로워로 수정 * Style: 변수이름 알맞게 수정 및 src 표기 수정 * Design: 피드페이지 - 그리드컨텐츠 리스트 스켈레톤 UI 수정 * Style: 불필요한 파일 삭제, 일부 컴포넌트 컨벤션 수정 * Feat: 팔로우 버튼 disabled 적용 * Refactor: 피드페이지 리스트전체조회 쿼리 staleTime 설정, unmount시 쿼리초기화 로직 제거 * Chore: 리액트쿼리 devtools 적용 * Feat: 리스트 생성, 수정, 삭제 시 리스트 전체조회 쿼리 무효화(invalidateQueries) 로직 추가 * Refactor: 카테고리 조회 staleTime 설정, 스켈레톤 적용 시 isLoading 속성으로 변경 --- src/app/_context/CommonProvider.tsx | 2 + .../ListDetailOuter/OpenBottomSheetButton.tsx | 2 +- src/app/list/[listId]/edit/page.tsx | 6 ++- src/app/list/create/page.tsx | 13 ++++- .../[userId]/_components/Categories.css.ts | 7 +++ .../user/[userId]/_components/Categories.tsx | 21 ++++---- .../_components/CategoriesSkeleton.tsx | 15 ------ .../user/[userId]/_components/Content.css.ts | 7 +++ src/app/user/[userId]/_components/Content.tsx | 25 ++++----- .../[userId]/_components/FollowButton.tsx | 6 +-- .../_components/MasonryGridSkeleton.tsx | 15 ------ .../user/[userId]/_components/Profile.css.ts | 12 +++++ src/app/user/[userId]/_components/Profile.tsx | 53 +++++++++---------- .../[userId]/_components/ProfileSkeleton.tsx | 14 ----- .../_components/SkeletonDesign.css.ts | 27 ---------- .../floatingButton/ArrowUpFloatingButton.tsx | 6 ++- .../PlusOptionFloatingButton.tsx | 7 ++- 17 files changed, 98 insertions(+), 140 deletions(-) delete mode 100644 src/app/user/[userId]/_components/CategoriesSkeleton.tsx delete mode 100644 src/app/user/[userId]/_components/MasonryGridSkeleton.tsx delete mode 100644 src/app/user/[userId]/_components/ProfileSkeleton.tsx delete mode 100644 src/app/user/[userId]/_components/SkeletonDesign.css.ts diff --git a/src/app/_context/CommonProvider.tsx b/src/app/_context/CommonProvider.tsx index 895fab9f..a19c7149 100644 --- a/src/app/_context/CommonProvider.tsx +++ b/src/app/_context/CommonProvider.tsx @@ -1,6 +1,7 @@ 'use client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactNode } from 'react'; import { CookiesProvider } from 'react-cookie'; @@ -10,6 +11,7 @@ export default function CommonProvider({ children }: { children: ReactNode }) { return ( {children} + ); } diff --git a/src/app/list/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx b/src/app/list/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx index 8876c59c..c530ee68 100644 --- a/src/app/list/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx +++ b/src/app/list/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx @@ -48,7 +48,7 @@ export default function OpenBottomSheetButton({ listId, isCollaborator }: OpenBo const deleteCommentMutation = useMutation({ mutationFn: () => deleteList(listId), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getAllList, user.id] }); + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getAllList, user.id + ''] }); }, }); diff --git a/src/app/list/[listId]/edit/page.tsx b/src/app/list/[listId]/edit/page.tsx index 36b856b0..83a4c651 100644 --- a/src/app/list/[listId]/edit/page.tsx +++ b/src/app/list/[listId]/edit/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { FieldErrors, FormProvider, useForm } from 'react-hook-form'; -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useRouter, useParams } from 'next/navigation'; import CreateItem from '@/app/list/create/_components/CreateItem'; @@ -20,6 +20,7 @@ export type FormErrors = FieldErrors; export default function EditPage() { const router = useRouter(); + const queryClient = useQueryClient(); const param = useParams<{ listId: string }>(); const { user } = useUser(); @@ -144,6 +145,9 @@ export default function EditPage() { } = useMutation({ mutationFn: updateList, onSettled: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.getAllList, user.id + ''], + }); router.replace(`/list/${param?.listId}`); }, }); diff --git a/src/app/list/create/page.tsx b/src/app/list/create/page.tsx index 53fe1ffd..7d2f1179 100644 --- a/src/app/list/create/page.tsx +++ b/src/app/list/create/page.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { FieldErrors, FormProvider, useForm } from 'react-hook-form'; -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/navigation'; import CreateItem from '@/app/list/create/_components/CreateItem'; @@ -10,14 +10,18 @@ import CreateList from '@/app/list/create/_components/CreateList'; import { ItemImagesType, ListCreateType } from '@/lib/types/listType'; import toasting from '@/lib/utils/toasting'; import toastMessage from '@/lib/constants/toastMessage'; +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; import createList from '@/app/_api/list/createList'; import uploadItemImages from '@/app/_api/list/uploadItemImages'; import { useLanguage } from '@/store/useLanguage'; +import { useUser } from '@/store/useUser'; export type FormErrors = FieldErrors; export default function CreatePage() { const { language } = useLanguage(); + const { user: userMeData } = useUser(); + const queryClient = useQueryClient(); const [step, setStep] = useState<'list' | 'item'>('list'); const router = useRouter(); @@ -124,6 +128,13 @@ export default function CreatePage() { imageFileList: formatData().imageFileList, }); } + queryClient.invalidateQueries({ + queryKey: [ + QUERY_KEYS.getAllList, + userMeData.id + '', + formatData().listData.collaboratorIds.length === 0 ? 'my' : 'collabo', + ], + }); router.replace(`/list/${data.listId}`); }, onError: () => { diff --git a/src/app/user/[userId]/_components/Categories.css.ts b/src/app/user/[userId]/_components/Categories.css.ts index 7b29855c..454b2903 100644 --- a/src/app/user/[userId]/_components/Categories.css.ts +++ b/src/app/user/[userId]/_components/Categories.css.ts @@ -15,6 +15,13 @@ export const container = style({ }, }); +export const skeletonContainer = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '1.5rem', +}); + export const button = style({ padding: '0.8rem 1.2rem', diff --git a/src/app/user/[userId]/_components/Categories.tsx b/src/app/user/[userId]/_components/Categories.tsx index a9b76246..aeb77fcb 100644 --- a/src/app/user/[userId]/_components/Categories.tsx +++ b/src/app/user/[userId]/_components/Categories.tsx @@ -1,11 +1,5 @@ -'use client'; - -/** - TODO - - [ ] 클릭했을때 로직 (상위요소에 핸들러 고민) (리팩토링) - */ - import { useQuery } from '@tanstack/react-query'; +import { Skeleton } from '@mui/material'; import * as styles from './Categories.css'; @@ -13,17 +7,16 @@ import getCategories from '@/app/_api/category/getCategories'; import { CategoryType } from '@/lib/types/categoriesType'; import { QUERY_KEYS } from '@/lib/constants/queryKeys'; -import CategoriesSkeleton from './CategoriesSkeleton'; - interface CategoriesProps { handleFetchListsOnCategory: (category: string) => void; selectedCategory: string; } export default function Categories({ handleFetchListsOnCategory, selectedCategory }: CategoriesProps) { - const { data, isFetching } = useQuery({ + const { data, isLoading } = useQuery({ queryKey: [QUERY_KEYS.getCategories], queryFn: getCategories, + staleTime: Infinity, // 카테고리는 자주 변하는 데이터가 아니므로 }); const handleChangeCategory = (category: string) => () => { @@ -32,8 +25,12 @@ export default function Categories({ handleFetchListsOnCategory, selectedCategor return (
- {isFetching ? ( - + {isLoading ? ( +
+ {new Array(4).fill(0).map((_, index) => ( + + ))} +
) : ( data?.map((category) => (
- ); -} diff --git a/src/app/user/[userId]/_components/Content.css.ts b/src/app/user/[userId]/_components/Content.css.ts index 3eb240d4..5109d5ed 100644 --- a/src/app/user/[userId]/_components/Content.css.ts +++ b/src/app/user/[userId]/_components/Content.css.ts @@ -67,6 +67,13 @@ export const cards = style({ textAlign: 'center', }); +export const gridSkeletonContainer = style({ + display: 'grid', + gridTemplateColumns: 'repeat(2, 1fr)', + rowGap: '1.6rem', + columnGap: '1.2rem', +}); + export const nodataContainer = style({ minHeight: '250px', }); diff --git a/src/app/user/[userId]/_components/Content.tsx b/src/app/user/[userId]/_components/Content.tsx index 5e98aabf..5c41bf05 100644 --- a/src/app/user/[userId]/_components/Content.tsx +++ b/src/app/user/[userId]/_components/Content.tsx @@ -1,14 +1,16 @@ 'use client'; import Link from 'next/link'; -import { useEffect, useMemo, useState } from 'react'; -import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useMemo, useState } from 'react'; +import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { MasonryGrid } from '@egjs/react-grid'; +import { Skeleton } from '@mui/material'; import * as styles from './Content.css'; import Card from './Card'; import Categories from './Categories'; +import NoDataComponent from '@/components/NoData/NoDataComponent'; import getUserOne from '@/app/_api/user/getUserOne'; import getAllList from '@/app/_api/list/getAllList'; @@ -18,8 +20,6 @@ import { UserType } from '@/lib/types/userProfileType'; import { AllListType } from '@/lib/types/listType'; import useIntersectionObserver from '@/hooks/useIntersectionObserver'; -import MasonryGridSkeleton from './MasonryGridSkeleton'; -import NoDataComponent from '@/components/NoData/NoDataComponent'; import { userLocale } from '@/app/user/locale'; import { useLanguage } from '@/store/useLanguage'; @@ -32,7 +32,6 @@ const DEFAULT_CATEGORY = 'entire'; export default function Content({ userId, type }: ContentProps) { const { language } = useLanguage(); - const queryClient = useQueryClient(); const [selectedCategory, setSelectedCategory] = useState(DEFAULT_CATEGORY); const { data: userData } = useQuery({ @@ -52,6 +51,7 @@ export default function Content({ userId, type }: ContentProps) { }, initialPageParam: null, getNextPageParam: (lastPage) => (lastPage.hasNext ? lastPage.cursorUpdatedDate : null), + staleTime: 1000 * 60 * 5, // 5분 설정 }); const lists = useMemo(() => { @@ -72,15 +72,6 @@ export default function Content({ userId, type }: ContentProps) { setSelectedCategory(category); }; - useEffect(() => { - return () => { - queryClient.removeQueries({ - queryKey: [QUERY_KEYS.getAllList, userId, type, selectedCategory], - exact: true, - }); - }; - }, [queryClient, selectedCategory, type, userId]); - return (
@@ -101,7 +92,11 @@ export default function Content({ userId, type }: ContentProps) { )}
{isLoading ? ( - +
+ {new Array(4).fill(0).map((_, index) => ( + + ))} +
) : ( {lists.map((list) => ( diff --git a/src/app/user/[userId]/_components/FollowButton.tsx b/src/app/user/[userId]/_components/FollowButton.tsx index 7095956c..68bd174a 100644 --- a/src/app/user/[userId]/_components/FollowButton.tsx +++ b/src/app/user/[userId]/_components/FollowButton.tsx @@ -1,5 +1,3 @@ -'use client'; - import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; @@ -26,9 +24,8 @@ interface FollowButtonProps { } export default function FollowButton({ isFollowed, userId }: FollowButtonProps) { - const { language } = useLanguage(); - const queryClient = useQueryClient(); + const { language } = useLanguage(); const { user: userMe } = useUser(); const { isOn, handleSetOff, handleSetOn } = useBooleanOutput(); @@ -85,6 +82,7 @@ export default function FollowButton({ isFollowed, userId }: FollowButtonProps) diff --git a/src/app/user/[userId]/_components/MasonryGridSkeleton.tsx b/src/app/user/[userId]/_components/MasonryGridSkeleton.tsx deleted file mode 100644 index ebdf3ea1..00000000 --- a/src/app/user/[userId]/_components/MasonryGridSkeleton.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Skeleton } from '@mui/material'; -import * as styles from './SkeletonDesign.css'; - -export default function MasonryGridSkeleton() { - return ( -
- - - - - - -
- ); -} diff --git a/src/app/user/[userId]/_components/Profile.css.ts b/src/app/user/[userId]/_components/Profile.css.ts index 47bef79e..6148000f 100644 --- a/src/app/user/[userId]/_components/Profile.css.ts +++ b/src/app/user/[userId]/_components/Profile.css.ts @@ -48,6 +48,18 @@ export const profileContainer = style({ }, }); +export const skeletonProfileContainer = style({ + paddingBottom: '3rem', + display: 'flex', + alignItems: 'center', + gap: '1.2rem', +}); + +export const skeletonTextContainer = style({ + display: 'flex', + flexDirection: 'column', +}); + export const icon = style({ cursor: 'pointer', }); diff --git a/src/app/user/[userId]/_components/Profile.tsx b/src/app/user/[userId]/_components/Profile.tsx index b61c8ad4..586923a7 100644 --- a/src/app/user/[userId]/_components/Profile.tsx +++ b/src/app/user/[userId]/_components/Profile.tsx @@ -1,17 +1,18 @@ 'use client'; import Image from 'next/image'; +import Link from 'next/link'; import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { assignInlineVars } from '@vanilla-extract/dynamic'; import { AxiosError } from 'axios'; +import { Skeleton } from '@mui/material'; import * as styles from './Profile.css'; -import * as modalStyles from '@/components/Modal/ModalButton.css'; import FollowButton from './FollowButton'; +import Modal from '@/components/Modal/Modal'; import SettingIcon from '/public/icons/setting.svg'; -import ProfileSkeleton from './ProfileSkeleton'; import useMoveToPage from '@/hooks/useMoveToPage'; import getUserOne from '@/app/_api/user/getUserOne'; @@ -20,19 +21,18 @@ import { QUERY_KEYS } from '@/lib/constants/queryKeys'; import { UserType } from '@/lib/types/userProfileType'; import numberFormatter from '@/lib/utils/numberFormatter'; -import Modal from '@/components/Modal/Modal'; import { userLocale } from '@/app/user/locale'; import { useLanguage } from '@/store/useLanguage'; export default function Profile({ userId }: { userId: number }) { const { language } = useLanguage(); - const [hasError, setHasError] = useState(false); + const [hasImageError, setHasImageError] = useState(false); const { onClickMoveToPage } = useMoveToPage(); const fallbackProfileImageSrc = '/images/fallback_profileImage.webp'; const fallbackBackgroundImageSrc = '/images/fallback_backgroundImage.webp'; - const { data, isFetching, error, isError } = useQuery({ + const { data, error, isError, isLoading } = useQuery({ queryKey: [QUERY_KEYS.userOne, userId], queryFn: () => getUserOne(userId), retry: 1, @@ -43,18 +43,13 @@ export default function Profile({ userId }: { userId: number }) { return ( {error.response?.data.detail} -
- -
+ {userLocale[language].confirm}
); } const handleImageError = () => { - // TODO onError일때 적용할 이미지 공통 로직 만들기 - setHasError(true); + setHasImageError(true); }; return ( @@ -64,18 +59,18 @@ export default function Profile({ userId }: { userId: number }) { [styles.imageUrl]: `url(${data ? data?.backgroundImageUrl : fallbackBackgroundImageSrc})`, })} > -
- {data?.isOwner && ( - - )} -
+ + {data?.isOwner && } +
- {isFetching ? ( - + {isLoading ? ( +
+ +
+ + +
+
) : ( <>
@@ -83,7 +78,7 @@ export default function Profile({ userId }: { userId: number }) { {data?.profileImageUrl ? ( {userLocale[language].profileImageAlt}}
-
+ {data?.followingCount !== undefined && numberFormatter(data.followingCount, 'ko')} {userLocale[language].following} -
-
+ + {data?.followerCount !== undefined && numberFormatter(data.followerCount, 'ko')} - {userLocale[language].follow} -
+ {userLocale[language].follower} +
diff --git a/src/app/user/[userId]/_components/ProfileSkeleton.tsx b/src/app/user/[userId]/_components/ProfileSkeleton.tsx deleted file mode 100644 index 8afc0b6e..00000000 --- a/src/app/user/[userId]/_components/ProfileSkeleton.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Skeleton } from '@mui/material'; -import * as styles from './SkeletonDesign.css'; - -export default function ProfileSkeleton() { - return ( -
- -
- - -
-
- ); -} diff --git a/src/app/user/[userId]/_components/SkeletonDesign.css.ts b/src/app/user/[userId]/_components/SkeletonDesign.css.ts deleted file mode 100644 index b2fc0a9b..00000000 --- a/src/app/user/[userId]/_components/SkeletonDesign.css.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { style } from '@vanilla-extract/css'; - -export const gridContainer = style({ - display: 'grid', - gridTemplateColumns: 'repeat(2, 1fr)', - rowGap: '1.6rem', - columnGap: '1.2rem', -}); - -export const categoryContainer = style({ - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - gap: '1.5rem', -}); - -export const profileContainer = style({ - paddingBottom: '3rem', - display: 'flex', - alignItems: 'center', - gap: '1.2rem', -}); - -export const textContainer = style({ - display: 'flex', - flexDirection: 'column', -}); diff --git a/src/components/floatingButton/ArrowUpFloatingButton.tsx b/src/components/floatingButton/ArrowUpFloatingButton.tsx index 84431574..03d84214 100644 --- a/src/components/floatingButton/ArrowUpFloatingButton.tsx +++ b/src/components/floatingButton/ArrowUpFloatingButton.tsx @@ -4,13 +4,15 @@ import { useState } from 'react'; import * as styles from './FloatingContainer.css'; import ArrowUpIcon from '/public/icons/arrow_up.svg'; + import useThrottle from '@/hooks/useThrottle'; -import { commonLocale } from '@/components/locale'; import { useLanguage } from '@/store/useLanguage'; +import { commonLocale } from '@/components/locale'; + export default function ArrowUpFloatingButton() { - const { language } = useLanguage(); const [isVisible, setIsVisible] = useState(false); + const { language } = useLanguage(); const visibleButton = () => { if (window.scrollY < 700) { diff --git a/src/components/floatingButton/PlusOptionFloatingButton.tsx b/src/components/floatingButton/PlusOptionFloatingButton.tsx index 434ed74c..21bd625d 100644 --- a/src/components/floatingButton/PlusOptionFloatingButton.tsx +++ b/src/components/floatingButton/PlusOptionFloatingButton.tsx @@ -9,22 +9,21 @@ import ShareAltIcon from '/public/icons/share_alt.svg'; import WriteIcon from '/public/icons/write.svg'; import useBooleanOutput from '@/hooks/useBooleanOutput'; -import toasting from '@/lib/utils/toasting'; -import toastMessage from '@/lib/constants/toastMessage'; import { useUser } from '@/store/useUser'; +import { useLanguage } from '@/store/useLanguage'; import copyUrl from '@/lib/utils/copyUrl'; import LoginModal from '@/components/login/LoginModal'; import Modal from '@/components/Modal/Modal'; import { commonLocale } from '@/components/locale'; -import { useLanguage } from '@/store/useLanguage'; function FloatingMenu() { - const { language } = useLanguage(); const router = useRouter(); const path = usePathname(); + const { user } = useUser(); const { isOn, handleSetOn, handleSetOff } = useBooleanOutput(); + const { language } = useLanguage(); const handleSharePage = () => { // TODO 카카오 공유하기 기능으로 변경하기 From 47578e8ca701629784ed21e44f33c007ec4fecf7 Mon Sep 17 00:00:00 2001 From: Eugene Ahn <70089733+Eugene-A-01@users.noreply.github.com> Date: Fri, 15 Mar 2024 21:43:43 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Hotfix:=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20API=20=EC=9A=94=EC=B2=AD=EC=8B=9C=20?= =?UTF-8?q?=EC=BD=9C=EB=9D=BC=EB=B3=B4=EB=A0=88=EC=9D=B4=ED=84=B0=20ID=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=97=90=20=EB=B3=B8=EC=9D=B8ID=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=20(#211)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/list/[listId]/edit/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/list/[listId]/edit/page.tsx b/src/app/list/[listId]/edit/page.tsx index 83a4c651..143afee5 100644 --- a/src/app/list/[listId]/edit/page.tsx +++ b/src/app/list/[listId]/edit/page.tsx @@ -63,7 +63,7 @@ export default function EditPage() { //데이터 쪼개기 const listData: ListEditType = { ...originData, - collaboratorIds: originData.collaboratorIds.length === 0 ? [] : [...originData.collaboratorIds, user.id], + collaboratorIds: originData.collaboratorIds, items: originData.items.map(({ imageUrl, ...rest }) => { if (typeof imageUrl === 'string') { return {