From 6ad4859544bcb0adff2fef1a50fc8d39dcba1f9b Mon Sep 17 00:00:00 2001 From: wynter24 Date: Thu, 27 Feb 2025 20:09:05 +0900 Subject: [PATCH 01/12] =?UTF-8?q?=F0=9F=92=84[Design]=20=EC=B0=9C=20UI=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/card/Card.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index adefce30..9b76ba01 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -7,7 +7,7 @@ import Avatar from '../avatar/Avatar'; import { LocationIcon, HostIcon, - // HeartIcon, + HeartIcon, RatingIcon, OnlineIcon, } from '../../../public/icons'; @@ -52,11 +52,10 @@ function CardBox({ children, className = '', ...props }: CardBoxProps) { function CardImage({ url, alt = '모임 이미지', - // isLiked, - // onLikeClick, + isLiked, + onLikeClick, className, isPast, - // isHost, ...props }: CardImageProps) { return ( @@ -74,11 +73,11 @@ function CardImage({ fill className={twMerge('object-cover', isPast && 'grayscale')} /> - {/* {isLiked !== undefined && !isHost && ( + {isLiked !== undefined && (
- )} */} + )} ); } From a8494d5ca6f23a53b635073a133c982027cb6229 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sun, 9 Mar 2025 15:51:35 +0900 Subject: [PATCH 02/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20bookclub?= =?UTF-8?q?=20=EC=84=9C=EB=B2=84=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?axios=EB=A1=9C=20fetching=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookclub/page.tsx | 10 +++++++--- src/lib/utils/fetchBookClubs.ts | 35 +++++++++++++-------------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index ea8484ec..314f4269 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -7,6 +7,8 @@ import { QueryClient, } from '@tanstack/react-query'; import { getServerSideToken } from '@/lib/utils/getServerSideToken'; +import { Suspense } from 'react'; +import Loading from '@/components/loading/Loading'; export default async function Home() { const queryClient = new QueryClient(); @@ -18,8 +20,10 @@ export default async function Home() { }); return ( - - - + }> + + + + ); } diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index a0dba2e1..18a4c3b3 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -1,31 +1,24 @@ import { BookClubParams } from '@/types/bookclubs'; +import axios from 'axios'; export async function fetchBookClubs(filters: BookClubParams, token?: string) { try { - // filters 객체를 URLSearchParams로 변환 - const queryParams = new URLSearchParams( - Object.entries(filters) - .filter(([, value]) => value !== undefined && value !== null) - .map(([key, value]) => [key, String(value)]), - ).toString(); + const baseURL = + typeof window === 'undefined' + ? process.env.NEXT_PUBLIC_API_URL // 서버사이드일 때 전체 URL + : ''; // 클라이언트일 때는 상대 경로 사용 - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?${queryParams}`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }, + const response = await axios.get(`${baseURL}/book-clubs`, { + params: filters, + headers: { + ...(token ? { Authorization: `Bearer ${token}` } : {}), + 'Cache-Control': 'no-cache', + Pragma: 'no-cache', }, - ); + }); - if (!res.ok) { - throw new Error(`HTTP error! status: ${res.status}`); - } - - const response = await res.json(); - return response.bookClubs; + console.log('서버 응답:', response.data.bookClubs); // 응답 데이터 확인 + return response.data.bookClubs; } catch (error) { if (process.env.NODE_ENV === 'development') { console.error('Error:', error); // 개발 환경에서만 로그 출력 From 722a8b759dcba2ff045ff189e391fe0aee24353d Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sun, 9 Mar 2025 20:13:21 +0900 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=90=9B[Fix]=20=EB=82=99=EA=B4=80?= =?UTF-8?q?=EC=A0=81=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=ED=83=80=EC=9E=85=20=ED=86=B5=EC=9D=BC=20?= =?UTF-8?q?#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/likeOptimisticUpdate.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/api/book-club/react-query/likeOptimisticUpdate.ts b/src/api/book-club/react-query/likeOptimisticUpdate.ts index 5c02550d..34ef89ee 100644 --- a/src/api/book-club/react-query/likeOptimisticUpdate.ts +++ b/src/api/book-club/react-query/likeOptimisticUpdate.ts @@ -12,19 +12,20 @@ export const likeOnMutate = async ( const listQueryKey = ['bookClubs', 'list', filter || DEFAULT_FILTERS]; const detailQueryKey = bookClubs.detail(id).queryKey; + const cachedData = queryClient.getQueryData(listQueryKey); + console.log('캐시된 데이터:', cachedData); + // 기존 요청을 취소(데이터 충돌 방지) await queryClient.cancelQueries({ queryKey: listQueryKey }); await queryClient.cancelQueries({ queryKey: detailQueryKey }); // 기존 캐시 데이터 저장 - const previousBookClubs = queryClient.getQueryData<{ bookClubs: BookClub[] }>( - listQueryKey, - ); + const previousBookClubs = queryClient.getQueryData(listQueryKey); const previousDetail = queryClient.getQueryData(detailQueryKey); // 캐시 데이터 업데이트 if (previousBookClubs) { - queryClient.setQueryData(listQueryKey, (old: any) => + queryClient.setQueryData(listQueryKey, (old: BookClub[] | undefined) => old?.map((club: BookClub) => club.id === id ? { ...club, isLiked } : club, ), @@ -42,7 +43,7 @@ export const likeOnError = ( queryClient: QueryClient, id: number, context: { - previousBookClubs?: { bookClubs: BookClub[] }; + previousBookClubs?: BookClub[]; previousDetail?: BookClub; }, ) => { From 83f84ab9985af06c5b1346221640819d8ffba15c Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 11 Mar 2025 14:56:46 +0900 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=90=9B[Fix]=20useEffect=EB=A1=9C=20?= =?UTF-8?q?hydration=20error=20=ED=95=B4=EA=B2=B0=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookclub/components/BookClubMainPage.tsx | 12 ++++++++---- src/features/bookclub/components/ClubListSection.tsx | 11 +---------- .../club-details/components/HeaderSection.tsx | 9 ++------- src/lib/utils/fetchBookClubs.ts | 1 - 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index 96973ab1..fb4e8874 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -9,21 +9,25 @@ import { useRouter } from 'next/navigation'; import Loading from '@/components/loading/Loading'; import { useQuery } from '@tanstack/react-query'; import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; +import { useEffect, useState } from 'react'; function BookClubMainPage() { const { filters, updateFilters } = useBookClubList(); const { data, isLoading, isFetching } = useQuery({ queryKey: ['bookClubs', 'list', filters], queryFn: () => fetchBookClubs(filters), - // enabled: false, // ✅ 서버에서 이미 가져왔기 때문에 클라이언트에서 다시 요청하지 않음 - // refetchOnMount: false, // ✅ 마운트 시 다시 데이터를 불러오지 않음 - staleTime: 1000 * 60, }); + // console.log('클라이언트 데이터:', data); // 클라이언트의 데이터 확인 + const [isHydrated, setIsHydrated] = useState(false); const router = useRouter(); const user = useAuthStore((state) => state.user); const userName = user?.nickname || '북코'; + useEffect(() => { + setIsHydrated(true); + }, []); + const handleFilterChange = (newFilter: Partial) => { updateFilters(newFilter); }; @@ -50,7 +54,7 @@ function BookClubMainPage() { } /> - {isLoading || isFetching ? ( + {isLoading || isFetching || !isHydrated ? (
diff --git a/src/features/bookclub/components/ClubListSection.tsx b/src/features/bookclub/components/ClubListSection.tsx index 856c7699..e1602769 100644 --- a/src/features/bookclub/components/ClubListSection.tsx +++ b/src/features/bookclub/components/ClubListSection.tsx @@ -10,7 +10,6 @@ import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useLikeClub, useLikeWithAuthCheck, useUnLikeClub } from '@/lib/hooks'; import { useAuthStore } from '@/store/authStore'; import PopUp from '@/components/pop-up/PopUp'; -import { queryClient } from '@/lib/utils/reactQueryProvider'; interface ClubListSectionProps { bookClubs: BookClub[]; @@ -19,6 +18,7 @@ interface ClubListSectionProps { function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) { const router = useRouter(); + const { isLikePopUpOpen, likePopUpLabel, @@ -31,13 +31,10 @@ function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) { useEffect(() => { checkLoginStatus(); - console.log('메인 페이지: ', bookClubs); }, [checkLoginStatus]); const today = useMemo(() => new Date(), []); - // console.log('🔍 ClubListSection 데이터:', bookClubs); - const handleLikeClub = (isLiked: boolean, id: number) => { if (!isLoggedIn) { onShowAuthPopUp(); @@ -49,12 +46,6 @@ function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) { } else { onConfirmLike(id); } - - queryClient.setQueryData(['bookClubs', 'list', filter], (oldData: any) => - oldData.map((club: BookClub) => - club.id === id ? { ...club, isLiked: !isLiked } : club, - ), - ); }; const handleLikePopUpConfirm = () => { diff --git a/src/features/club-details/components/HeaderSection.tsx b/src/features/club-details/components/HeaderSection.tsx index ea49dace..542d48ba 100644 --- a/src/features/club-details/components/HeaderSection.tsx +++ b/src/features/club-details/components/HeaderSection.tsx @@ -17,7 +17,6 @@ import { useLikeWithAuthCheck, useUnLikeClub, } from '@/lib/hooks/index'; -import { useLikeContext } from '@/lib/contexts/LikeContext'; interface HeaderSectionProps { clubInfo: BookClub; @@ -46,8 +45,6 @@ function HeaderSection({ clubInfo, idAsNumber }: HeaderSectionProps) { } = useLikeWithAuthCheck(); const { onConfirmLike } = useLikeClub(); const { onConfirmUnLike } = useUnLikeClub(); - const { likedClubs, toggleLike } = useLikeContext(); - const isLiked = likedClubs?.has(clubInfo.id) ?? clubInfo.isLiked; const { isLoggedIn, checkLoginStatus, user } = useAuthStore(); @@ -68,14 +65,12 @@ function HeaderSection({ clubInfo, idAsNumber }: HeaderSectionProps) { !isLoggedIn ? router.push('/login') : handleJoin(clubInfo.id); }; - const handleLikeClub = () => { + const handleLikeClub = (isLiked: boolean) => { if (!isLoggedIn) { onShowAuthPopUp(); return; } - toggleLike(clubInfo.id, !isLiked); // ✅ 전역 상태 업데이트 - if (isLiked) { onConfirmUnLike(clubInfo.id); } else { @@ -107,7 +102,7 @@ function HeaderSection({ clubInfo, idAsNumber }: HeaderSectionProps) { clubInfo.endDate, new Date(), // TODO: new Date() 최적화 후 수정 ), - onLikeClick: handleLikeClub, + onLikeClick: () => handleLikeClub(clubInfo.isLiked), host: { id: clubInfo.hostId, name: clubInfo.hostNickname, diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index 18a4c3b3..948e369d 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -17,7 +17,6 @@ export async function fetchBookClubs(filters: BookClubParams, token?: string) { }, }); - console.log('서버 응답:', response.data.bookClubs); // 응답 데이터 확인 return response.data.bookClubs; } catch (error) { if (process.env.NODE_ENV === 'development') { From aa53c13ba9657928e6b042490ec19bb41f558e3a Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 11 Mar 2025 15:00:33 +0900 Subject: [PATCH 05/12] =?UTF-8?q?=F0=9F=94=A5[Remove]=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=B0=9C=EA=B4=80=EB=A0=A8=20context=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/contexts/LikeContext.tsx | 99 -------------------------------- 1 file changed, 99 deletions(-) delete mode 100644 src/lib/contexts/LikeContext.tsx diff --git a/src/lib/contexts/LikeContext.tsx b/src/lib/contexts/LikeContext.tsx deleted file mode 100644 index 5f49e169..00000000 --- a/src/lib/contexts/LikeContext.tsx +++ /dev/null @@ -1,99 +0,0 @@ -'use client'; - -import React, { - createContext, - useState, - useContext, - useCallback, - useEffect, -} from 'react'; -import { useAuthStore } from '@/store/authStore'; - -interface LikeContextType { - likedClubs: Set | undefined; - toggleLike: (clubId: number, isLiked?: boolean) => void; - isLiked: (clubId: number) => boolean; -} - -// 초깃값과 함께 컨텍스트 생성 -const LikeContext = createContext(undefined); - -export const LikeProvider: React.FC<{ children: React.ReactNode }> = ({ - children, -}) => { - const [likedClubs, setLikedClubs] = useState | undefined>( - undefined, - ); - const { isLoggedIn } = useAuthStore(); - - // // ✅ localStorage에서 찜한 목록 불러오기 (초기 로드) - useEffect(() => { - const storedLikes = localStorage.getItem('likedClubs'); - if (storedLikes) { - setLikedClubs(new Set(JSON.parse(storedLikes))); - } else { - setLikedClubs(new Set()); - } - }, []); - - // ✅ `localStorage`에서 찜 목록 다시 불러오기 (새로고침 시) - useEffect(() => { - if (typeof window !== 'undefined') { - const storedLikes = localStorage.getItem('likedClubs'); - setLikedClubs(storedLikes ? new Set(JSON.parse(storedLikes)) : new Set()); - } - }, []); - - // ✅ likedClubs가 변경될 때마다 `localStorage`에 저장 - useEffect(() => { - if (typeof window !== 'undefined' && likedClubs) { - localStorage.setItem( - 'likedClubs', - JSON.stringify(Array.from(likedClubs)), - ); - } - }, [likedClubs]); - - // ✅ 로그아웃 시 찜한 목록 초기화 - useEffect(() => { - if (!isLoggedIn) { - setLikedClubs(new Set()); // ✅ 찜한 상태 초기화 - localStorage.removeItem('likedClubs'); // ✅ localStorage에서도 삭제 - } - }, [isLoggedIn]); - - const toggleLike = useCallback((clubId: number, isLiked?: boolean) => { - setLikedClubs((prev) => { - if (!prev) return prev; // 로딩 중이면 변경 X - const newSet = new Set(prev); - if (isLiked !== undefined) { - isLiked ? newSet.add(clubId) : newSet.delete(clubId); - } else { - newSet.has(clubId) ? newSet.delete(clubId) : newSet.add(clubId); - } - localStorage.setItem('likedClubs', JSON.stringify(Array.from(newSet))); - return newSet; - }); - }, []); - - const isLiked = useCallback( - (clubId: number) => likedClubs?.has(clubId) ?? false, // `undefined`일 경우 `false` 반환 - [likedClubs], - ); - - // 컨텍스트 생서자로 데이터 제공 - return ( - - {children} - - ); -}; - -export const useLikeContext = () => { - // 컨텍스트 사용으로 데이터 얻기 - const context = useContext(LikeContext); - if (context === undefined) { - throw new Error('useLikeContext must be used within a LikeProvider'); - } - return context; -}; From e3dceda6a6e53b2eafbbe762dbb4e16904e09d16 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 11 Mar 2025 15:07:25 +0900 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=90=9B[Fix]=20=EC=B0=9C=20context?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/layout.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 547bf187..48a6dc59 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,7 +6,6 @@ import { Toast } from '@/components/toast/toast'; import { MSWComponent } from '@/components/MSWComponent'; import '@/styles/globals.css'; -import { LikeProvider } from '@/lib/contexts/LikeContext'; export const metadata: Metadata = { title: 'Bookco', @@ -26,13 +25,11 @@ export default function RootLayout({ strategy="beforeInteractive" /> - - -
- - {children} -
-
+ +
+ + {children} +
From 52c48d1e8090d46a77428fe837e26958edffe89e Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 11 Mar 2025 15:18:49 +0900 Subject: [PATCH 07/12] =?UTF-8?q?=F0=9F=8E=A8[Style]=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/likeOptimisticUpdate.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/api/book-club/react-query/likeOptimisticUpdate.ts b/src/api/book-club/react-query/likeOptimisticUpdate.ts index 34ef89ee..6d15ca3a 100644 --- a/src/api/book-club/react-query/likeOptimisticUpdate.ts +++ b/src/api/book-club/react-query/likeOptimisticUpdate.ts @@ -12,9 +12,6 @@ export const likeOnMutate = async ( const listQueryKey = ['bookClubs', 'list', filter || DEFAULT_FILTERS]; const detailQueryKey = bookClubs.detail(id).queryKey; - const cachedData = queryClient.getQueryData(listQueryKey); - console.log('캐시된 데이터:', cachedData); - // 기존 요청을 취소(데이터 충돌 방지) await queryClient.cancelQueries({ queryKey: listQueryKey }); await queryClient.cancelQueries({ queryKey: detailQueryKey }); From 95c263395cc45258e8dec714645c3301571b7486 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 11 Mar 2025 16:30:02 +0900 Subject: [PATCH 08/12] =?UTF-8?q?=E2=9C=A8[Feat]=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20errorboudary?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookclub/components/BookClubMainPage.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index fb4e8874..6c683438 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -10,10 +10,12 @@ import Loading from '@/components/loading/Loading'; import { useQuery } from '@tanstack/react-query'; import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; import { useEffect, useState } from 'react'; +import ErrorHandlingWrapper from '@/components/error/ErrorHandlingWrapper'; +import ErrorFallback from '@/components/error/ErrorFallback'; function BookClubMainPage() { const { filters, updateFilters } = useBookClubList(); - const { data, isLoading, isFetching } = useQuery({ + const { data, isLoading } = useQuery({ queryKey: ['bookClubs', 'list', filters], queryFn: () => fetchBookClubs(filters), }); @@ -54,14 +56,20 @@ function BookClubMainPage() { } /> - {isLoading || isFetching || !isHydrated ? ( + + {isLoading || !isHydrated ? (
) : ( -
- -
+ } + > +
+ +
+
)} ); From b366c7af14183b0e6cfd69acfad1e86b9fcf6de8 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 11 Mar 2025 16:35:00 +0900 Subject: [PATCH 09/12] =?UTF-8?q?=E2=9C=A8[Feat]=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=9A=94=EC=B2=AD=EC=97=90=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=B6=94=EA=B0=80=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/bookclub/components/BookClubMainPage.tsx | 6 +++++- src/lib/utils/fetchBookClubs.ts | 5 +---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index 6c683438..7bb92d4e 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -12,12 +12,16 @@ import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; import { useEffect, useState } from 'react'; import ErrorHandlingWrapper from '@/components/error/ErrorHandlingWrapper'; import ErrorFallback from '@/components/error/ErrorFallback'; +import { getCookie } from '@/features/auth/utils/cookies'; function BookClubMainPage() { const { filters, updateFilters } = useBookClubList(); const { data, isLoading } = useQuery({ queryKey: ['bookClubs', 'list', filters], - queryFn: () => fetchBookClubs(filters), + queryFn: () => { + const token = getCookie('auth_token'); + return fetchBookClubs(filters, token || undefined); + }, }); // console.log('클라이언트 데이터:', data); // 클라이언트의 데이터 확인 diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index 948e369d..66f0f815 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -3,10 +3,7 @@ import axios from 'axios'; export async function fetchBookClubs(filters: BookClubParams, token?: string) { try { - const baseURL = - typeof window === 'undefined' - ? process.env.NEXT_PUBLIC_API_URL // 서버사이드일 때 전체 URL - : ''; // 클라이언트일 때는 상대 경로 사용 + const baseURL = process.env.NEXT_PUBLIC_API_URL; const response = await axios.get(`${baseURL}/book-clubs`, { params: filters, From f570d6953444a6760cb6a06c9dc397e049479330 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 11 Mar 2025 16:51:00 +0900 Subject: [PATCH 10/12] =?UTF-8?q?=E2=9C=A8[Feat]=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=82=AC=EC=9D=B4=EB=93=9C=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookclub/error.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/app/bookclub/error.tsx diff --git a/src/app/bookclub/error.tsx b/src/app/bookclub/error.tsx new file mode 100644 index 00000000..a79ea965 --- /dev/null +++ b/src/app/bookclub/error.tsx @@ -0,0 +1,20 @@ +'use client'; + +import ErrorTemplate from '@/components/error/ErrorTemplate'; + +export default function BookClubError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ( + + ); +} From 0f69c79090c56e0b9443a7ddb4c222116bce0b55 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 11 Mar 2025 21:07:17 +0900 Subject: [PATCH 11/12] =?UTF-8?q?=E2=9C=85[Test]=20fetchBookClub=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20fetch?= =?UTF-8?q?=EC=97=90=EC=84=9C=20axios=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClub.test.ts | 60 +++++++++++++++++++---------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/lib/utils/fetchBookClub.test.ts b/src/lib/utils/fetchBookClub.test.ts index 1873767e..5867265e 100644 --- a/src/lib/utils/fetchBookClub.test.ts +++ b/src/lib/utils/fetchBookClub.test.ts @@ -1,55 +1,75 @@ import { mockBookClubs } from '@/mocks/mockDatas'; import { fetchBookClubs } from './fetchBookClubs'; import { DEFAULT_FILTERS } from '@/constants/filters'; +import axios from 'axios'; + +jest.mock('axios'); +const mockedAxios = axios as jest.Mocked; describe('fetchBookClubs', () => { beforeEach(() => { - global.fetch = jest.fn(); - }); - - afterEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); it('요청 성공 시 bookClubs를 반환해야 한다', async () => { - (global.fetch as jest.Mock).mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ bookClubs: mockBookClubs }), + mockedAxios.get.mockResolvedValue({ + data: { bookClubs: mockBookClubs }, }); const result = await fetchBookClubs(DEFAULT_FILTERS); - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith( - expect.stringContaining(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs?`), + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { - method: 'GET', + params: DEFAULT_FILTERS, headers: { - 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache', + Pragma: 'no-cache', }, }, ); expect(result).toEqual(mockBookClubs); }); + it('인증된 요청 시 토큰이 헤더에 포함되어야 한다', async () => { + mockedAxios.get.mockResolvedValue({ + data: { bookClubs: mockBookClubs }, + }); + + const token = 'test-token'; + await fetchBookClubs(DEFAULT_FILTERS, token); + + expect(mockedAxios.get).toHaveBeenCalledWith( + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, + { + params: DEFAULT_FILTERS, + headers: { + Authorization: `Bearer ${token}`, + 'Cache-Control': 'no-cache', + Pragma: 'no-cache', + }, + }, + ); + }); + it('HTTP 에러 발생 시 빈 배열을 반환해야 한다', async () => { - (global.fetch as jest.Mock).mockResolvedValue({ - ok: false, - status: 500, + mockedAxios.get.mockResolvedValue({ + response: { status: 500 }, }); const result = await fetchBookClubs(DEFAULT_FILTERS); - expect(fetch).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledTimes(1); expect(result).toEqual([]); }); - it('fetch 호출 중 에러 발생 시 빈 배열을 반환해야 한다', async () => { - (global.fetch as jest.Mock).mockRejectedValue(new Error('Network Error')); + it('네트워크 에러 발생 시 빈 배열을 반환해야 한다', async () => { + mockedAxios.get.mockResolvedValue(new Error('Network Error')); const result = await fetchBookClubs(DEFAULT_FILTERS); - expect(fetch).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledTimes(1); expect(result).toEqual([]); }); }); From a98a6405956a0a83ae56ad69174ac6fb6e76deb9 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Thu, 13 Mar 2025 15:06:57 +0900 Subject: [PATCH 12/12] =?UTF-8?q?=F0=9F=8E=A8[Style]=20=20=EB=AA=A8?= =?UTF-8?q?=EC=9E=84=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20suspense=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20#339?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookclub/page.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 314f4269..ea8484ec 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -7,8 +7,6 @@ import { QueryClient, } from '@tanstack/react-query'; import { getServerSideToken } from '@/lib/utils/getServerSideToken'; -import { Suspense } from 'react'; -import Loading from '@/components/loading/Loading'; export default async function Home() { const queryClient = new QueryClient(); @@ -20,10 +18,8 @@ export default async function Home() { }); return ( - }> - - - - + + + ); }