From 58426ac2fa93edfbbcf24f0aedc3a0cb188c22e5 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 13 Jan 2025 11:37:36 +0900 Subject: [PATCH 01/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20fetching=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookclub/page.tsx | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 91208bd1..c322215c 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,5 +1,35 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; -export default function Home() { - return ; +export default async function Home() { + console.log('SSR 함수 실행됨'); + + const DEFAULT_PAGE = 1; + const DEFAULT_SIZE = 10; + + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?page=${DEFAULT_PAGE}&size=${DEFAULT_SIZE}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + if (!res.ok) { + throw new Error(`HTTP error! status: ${res.status}`); + } + + const response = await res.json(); + const initialData = response.bookClubs; + + console.log('SSR로 가져온 데이터:', initialData); + + return ; + } catch (error) { + console.error('SSR 데이터 가져오기 실패:', error); + + return ; + } } From 8522c3060218376b08585b6826087a50268f84ac Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 14 Jan 2025 15:02:48 +0900 Subject: [PATCH 02/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20fetching?= =?UTF-8?q?=20=ED=95=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20useQuery?= =?UTF-8?q?=20=EC=B4=88=EA=B8=B0=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/bookclub/hooks/useFetchBookClubList.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 2b70ca45..5537816e 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -1,17 +1,21 @@ import { useState } from 'react'; -import { BookClubParams } from '@/types/bookclubs'; +import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; -const useBookClubList = () => { +const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { + // const [filters, setFilters] = useState(defaultFilters); const [filters, setFilters] = useState(DEFAULT_FILTERS); const { data, isLoading, error } = useQuery({ ...bookClubs.list(filters), + initialData: { bookClubs: initialData }, // SSR로 전달받은 초기 데이터 설정 + staleTime: 1000 * 60 * 5, // 5분 동안 데이터를 Fresh 상태로 유지 }); const clubList = data?.bookClubs; + console.log(clubList); const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); From cbaa13519974d6ec5589e260af5f8d77a51c9911 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Wed, 15 Jan 2025 10:57:33 +0900 Subject: [PATCH 03/48] =?UTF-8?q?=E2=9C=A8[Feat]=20srr=EB=A1=9C=20data=20f?= =?UTF-8?q?etching=20=ED=9B=84=20csr=EB=A1=9C=20data=20fetching=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=A4=91=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/queries.ts | 7 +++++-- src/app/bookclub/page.tsx | 16 +++++----------- .../bookclub/components/BookClubMainPage.tsx | 11 ++++++++--- .../bookclub/hooks/useFetchBookClubList.ts | 14 +++++++++++--- src/lib/constants/filters.ts | 2 +- src/types/bookclubs.ts | 2 +- 6 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/api/book-club/react-query/queries.ts b/src/api/book-club/react-query/queries.ts index 92f84752..8b84c6b7 100644 --- a/src/api/book-club/react-query/queries.ts +++ b/src/api/book-club/react-query/queries.ts @@ -6,8 +6,11 @@ import { bookClubMainAPI } from '@/api/book-club/bookClubMainAPI'; export const bookClubs = createQueryKeys('bookClubs', { list: (filters?: BookClubParams) => ({ - queryKey: [{ filters: filters || {} }], - queryFn: () => bookClubMainAPI.getBookClubs(filters), + queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], + queryFn: () => { + console.log('Fetching data with filters:', filters); + bookClubMainAPI.getBookClubs(filters); + }, }), detail: (bookClubId: number) => ({ diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index c322215c..8937fbfc 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -3,19 +3,13 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; export default async function Home() { console.log('SSR 함수 실행됨'); - const DEFAULT_PAGE = 1; - const DEFAULT_SIZE = 10; - try { - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?page=${DEFAULT_PAGE}&size=${DEFAULT_SIZE}`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', }, - ); + }); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index f434bbdf..07f31b3c 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -7,9 +7,14 @@ import ClubListSection from './ClubListSection'; import Button from '@/components/button/Button'; import { useRouter } from 'next/navigation'; import Loading from '@/components/loading/Loading'; +import { BookClub } from '@/types/bookclubs'; -function BookClubMainPage() { - const { clubList, isLoading, filters, updateFilters } = useBookClubList(); +function BookClubMainPage({ initialData }: { initialData: BookClub[] }) { + const { clubList, isLoading, isFetching, filters, updateFilters } = + useBookClubList({ + initialData, + // defaultFilters, + }); const router = useRouter(); @@ -43,7 +48,7 @@ function BookClubMainPage() { } /> - {isLoading ? ( + {isLoading || isFetching ? (
diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 5537816e..74d3d60f 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -3,20 +3,27 @@ import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; +import { bookClubMainAPI } from '@/api/book-club'; const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { - // const [filters, setFilters] = useState(defaultFilters); const [filters, setFilters] = useState(DEFAULT_FILTERS); - const { data, isLoading, error } = useQuery({ + const { data, isLoading, isFetching, error } = useQuery({ ...bookClubs.list(filters), + //[데이터 그룹 식별, 세부 데이터 유형, 추가 정보(보통 객체로 전달)] + queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], + queryFn: () => bookClubMainAPI.getBookClubs(filters), initialData: { bookClubs: initialData }, // SSR로 전달받은 초기 데이터 설정 - staleTime: 1000 * 60 * 5, // 5분 동안 데이터를 Fresh 상태로 유지 + staleTime: 0, + // enabled: !!filters, // filters가 존재할 때만 요청 실행 }); const clubList = data?.bookClubs; console.log(clubList); + // console.log('queryKey:', ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])]); + // console.log('filters: ',filters); + const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); }; @@ -24,6 +31,7 @@ const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { return { clubList, isLoading, + isFetching, error, filters, updateFilters, diff --git a/src/lib/constants/filters.ts b/src/lib/constants/filters.ts index 98505373..0ee948a3 100644 --- a/src/lib/constants/filters.ts +++ b/src/lib/constants/filters.ts @@ -5,6 +5,6 @@ export const DEFAULT_FILTERS: BookClubParams = { meetingType: 'ALL', order: 'DESC', page: 1, - size: 100, + size: 10, searchKeyword: '', }; diff --git a/src/types/bookclubs.ts b/src/types/bookclubs.ts index 81b9c090..e043a785 100644 --- a/src/types/bookclubs.ts +++ b/src/types/bookclubs.ts @@ -42,7 +42,7 @@ export interface BookClub { averageScore?: number; - clubStatus: 'pending' | 'confirmed' | 'closed'; // TODO: 내가 만든 모임에서 '모임 완료' 상태 추가 + clubStatus?: 'pending' | 'confirmed' | 'closed'; // TODO: 내가 만든 모임에서 '모임 완료' 상태 추가 isLiked: boolean; isInactive: boolean; isJoined: boolean; From 9b61a24c9d82b629e04cae2c1d2d886ef11fbc5a Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sat, 18 Jan 2025 15:37:10 +0900 Subject: [PATCH 04/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A8=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=A0=8C=EB=8D=94=EB=A7=81=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/queries.ts | 7 ++--- src/app/bookclub/page.tsx | 27 +++---------------- src/app/page.tsx | 7 +++-- .../bookclub/components/BookClubMainPage.tsx | 1 - .../bookclub/hooks/useFetchBookClubList.ts | 19 ++++--------- src/lib/utils/fetchBookClubs.ts | 20 ++++++++++++++ 6 files changed, 35 insertions(+), 46 deletions(-) create mode 100644 src/lib/utils/fetchBookClubs.ts diff --git a/src/api/book-club/react-query/queries.ts b/src/api/book-club/react-query/queries.ts index 8b84c6b7..92f84752 100644 --- a/src/api/book-club/react-query/queries.ts +++ b/src/api/book-club/react-query/queries.ts @@ -6,11 +6,8 @@ import { bookClubMainAPI } from '@/api/book-club/bookClubMainAPI'; export const bookClubs = createQueryKeys('bookClubs', { list: (filters?: BookClubParams) => ({ - queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], - queryFn: () => { - console.log('Fetching data with filters:', filters); - bookClubMainAPI.getBookClubs(filters); - }, + queryKey: [{ filters: filters || {} }], + queryFn: () => bookClubMainAPI.getBookClubs(filters), }), detail: (bookClubId: number) => ({ diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 8937fbfc..3693cc9d 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,29 +1,8 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; +import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; export default async function Home() { - console.log('SSR 함수 실행됨'); + const initialData = await fetchBookClubs(); - try { - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (!res.ok) { - throw new Error(`HTTP error! status: ${res.status}`); - } - - const response = await res.json(); - const initialData = response.bookClubs; - - console.log('SSR로 가져온 데이터:', initialData); - - return ; - } catch (error) { - console.error('SSR 데이터 가져오기 실패:', error); - - return ; - } + return ; } diff --git a/src/app/page.tsx b/src/app/page.tsx index 91208bd1..3693cc9d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,8 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; +import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; -export default function Home() { - return ; +export default async function Home() { + const initialData = await fetchBookClubs(); + + return ; } diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index 07f31b3c..6b864202 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -13,7 +13,6 @@ function BookClubMainPage({ initialData }: { initialData: BookClub[] }) { const { clubList, isLoading, isFetching, filters, updateFilters } = useBookClubList({ initialData, - // defaultFilters, }); const router = useRouter(); diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 74d3d60f..57c9e31b 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -3,33 +3,24 @@ import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; -import { bookClubMainAPI } from '@/api/book-club'; const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { const [filters, setFilters] = useState(DEFAULT_FILTERS); const { data, isLoading, isFetching, error } = useQuery({ ...bookClubs.list(filters), - //[데이터 그룹 식별, 세부 데이터 유형, 추가 정보(보통 객체로 전달)] - queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], - queryFn: () => bookClubMainAPI.getBookClubs(filters), - initialData: { bookClubs: initialData }, // SSR로 전달받은 초기 데이터 설정 - staleTime: 0, - // enabled: !!filters, // filters가 존재할 때만 요청 실행 + initialData: { bookClubs: initialData }, + initialDataUpdatedAt: 0, + refetchOnMount: false, + refetchOnWindowFocus: false, }); - const clubList = data?.bookClubs; - console.log(clubList); - - // console.log('queryKey:', ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])]); - // console.log('filters: ',filters); - const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); }; return { - clubList, + clubList: data?.bookClubs, isLoading, isFetching, error, diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts new file mode 100644 index 00000000..51ceb01b --- /dev/null +++ b/src/lib/utils/fetchBookClubs.ts @@ -0,0 +1,20 @@ +export async function fetchBookClubs() { + try { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + throw new Error(`HTTP error! status: ${res.status}`); + } + + const response = await res.json(); + return response.bookClubs; + } catch (error) { + console.error('데이터 가져오기 실패:', error); + return []; + } +} From 6d785dc22accfc2221203a9c6822d6a431374978 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 13 Jan 2025 11:37:36 +0900 Subject: [PATCH 05/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20fetching=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookclub/page.tsx | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 91208bd1..c322215c 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,5 +1,35 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; -export default function Home() { - return ; +export default async function Home() { + console.log('SSR 함수 실행됨'); + + const DEFAULT_PAGE = 1; + const DEFAULT_SIZE = 10; + + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?page=${DEFAULT_PAGE}&size=${DEFAULT_SIZE}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + if (!res.ok) { + throw new Error(`HTTP error! status: ${res.status}`); + } + + const response = await res.json(); + const initialData = response.bookClubs; + + console.log('SSR로 가져온 데이터:', initialData); + + return ; + } catch (error) { + console.error('SSR 데이터 가져오기 실패:', error); + + return ; + } } From b90275f4de34e6103dc43db9da385c635c55c985 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 14 Jan 2025 15:02:48 +0900 Subject: [PATCH 06/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20fetching?= =?UTF-8?q?=20=ED=95=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20useQuery?= =?UTF-8?q?=20=EC=B4=88=EA=B8=B0=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/bookclub/hooks/useFetchBookClubList.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 2b70ca45..5537816e 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -1,17 +1,21 @@ import { useState } from 'react'; -import { BookClubParams } from '@/types/bookclubs'; +import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; -const useBookClubList = () => { +const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { + // const [filters, setFilters] = useState(defaultFilters); const [filters, setFilters] = useState(DEFAULT_FILTERS); const { data, isLoading, error } = useQuery({ ...bookClubs.list(filters), + initialData: { bookClubs: initialData }, // SSR로 전달받은 초기 데이터 설정 + staleTime: 1000 * 60 * 5, // 5분 동안 데이터를 Fresh 상태로 유지 }); const clubList = data?.bookClubs; + console.log(clubList); const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); From f22837a4326b113f754f08ea70f8501a423f9612 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Wed, 15 Jan 2025 10:57:33 +0900 Subject: [PATCH 07/48] =?UTF-8?q?=E2=9C=A8[Feat]=20srr=EB=A1=9C=20data=20f?= =?UTF-8?q?etching=20=ED=9B=84=20csr=EB=A1=9C=20data=20fetching=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=A4=91=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/queries.ts | 7 +++++-- src/app/bookclub/page.tsx | 16 +++++----------- .../bookclub/components/BookClubMainPage.tsx | 11 ++++++++--- .../bookclub/hooks/useFetchBookClubList.ts | 14 +++++++++++--- src/lib/constants/filters.ts | 2 +- src/types/bookclubs.ts | 2 +- 6 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/api/book-club/react-query/queries.ts b/src/api/book-club/react-query/queries.ts index 92f84752..8b84c6b7 100644 --- a/src/api/book-club/react-query/queries.ts +++ b/src/api/book-club/react-query/queries.ts @@ -6,8 +6,11 @@ import { bookClubMainAPI } from '@/api/book-club/bookClubMainAPI'; export const bookClubs = createQueryKeys('bookClubs', { list: (filters?: BookClubParams) => ({ - queryKey: [{ filters: filters || {} }], - queryFn: () => bookClubMainAPI.getBookClubs(filters), + queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], + queryFn: () => { + console.log('Fetching data with filters:', filters); + bookClubMainAPI.getBookClubs(filters); + }, }), detail: (bookClubId: number) => ({ diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index c322215c..8937fbfc 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -3,19 +3,13 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; export default async function Home() { console.log('SSR 함수 실행됨'); - const DEFAULT_PAGE = 1; - const DEFAULT_SIZE = 10; - try { - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?page=${DEFAULT_PAGE}&size=${DEFAULT_SIZE}`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', }, - ); + }); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index f434bbdf..07f31b3c 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -7,9 +7,14 @@ import ClubListSection from './ClubListSection'; import Button from '@/components/button/Button'; import { useRouter } from 'next/navigation'; import Loading from '@/components/loading/Loading'; +import { BookClub } from '@/types/bookclubs'; -function BookClubMainPage() { - const { clubList, isLoading, filters, updateFilters } = useBookClubList(); +function BookClubMainPage({ initialData }: { initialData: BookClub[] }) { + const { clubList, isLoading, isFetching, filters, updateFilters } = + useBookClubList({ + initialData, + // defaultFilters, + }); const router = useRouter(); @@ -43,7 +48,7 @@ function BookClubMainPage() { } /> - {isLoading ? ( + {isLoading || isFetching ? (
diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 5537816e..74d3d60f 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -3,20 +3,27 @@ import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; +import { bookClubMainAPI } from '@/api/book-club'; const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { - // const [filters, setFilters] = useState(defaultFilters); const [filters, setFilters] = useState(DEFAULT_FILTERS); - const { data, isLoading, error } = useQuery({ + const { data, isLoading, isFetching, error } = useQuery({ ...bookClubs.list(filters), + //[데이터 그룹 식별, 세부 데이터 유형, 추가 정보(보통 객체로 전달)] + queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], + queryFn: () => bookClubMainAPI.getBookClubs(filters), initialData: { bookClubs: initialData }, // SSR로 전달받은 초기 데이터 설정 - staleTime: 1000 * 60 * 5, // 5분 동안 데이터를 Fresh 상태로 유지 + staleTime: 0, + // enabled: !!filters, // filters가 존재할 때만 요청 실행 }); const clubList = data?.bookClubs; console.log(clubList); + // console.log('queryKey:', ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])]); + // console.log('filters: ',filters); + const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); }; @@ -24,6 +31,7 @@ const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { return { clubList, isLoading, + isFetching, error, filters, updateFilters, diff --git a/src/lib/constants/filters.ts b/src/lib/constants/filters.ts index 98505373..0ee948a3 100644 --- a/src/lib/constants/filters.ts +++ b/src/lib/constants/filters.ts @@ -5,6 +5,6 @@ export const DEFAULT_FILTERS: BookClubParams = { meetingType: 'ALL', order: 'DESC', page: 1, - size: 100, + size: 10, searchKeyword: '', }; diff --git a/src/types/bookclubs.ts b/src/types/bookclubs.ts index 81b9c090..e043a785 100644 --- a/src/types/bookclubs.ts +++ b/src/types/bookclubs.ts @@ -42,7 +42,7 @@ export interface BookClub { averageScore?: number; - clubStatus: 'pending' | 'confirmed' | 'closed'; // TODO: 내가 만든 모임에서 '모임 완료' 상태 추가 + clubStatus?: 'pending' | 'confirmed' | 'closed'; // TODO: 내가 만든 모임에서 '모임 완료' 상태 추가 isLiked: boolean; isInactive: boolean; isJoined: boolean; From 06344dafbcaa09066b3eeaba5a503c1209f47743 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sat, 18 Jan 2025 15:37:10 +0900 Subject: [PATCH 08/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A8=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=A0=8C=EB=8D=94=EB=A7=81=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/queries.ts | 7 ++--- src/app/bookclub/page.tsx | 27 +++---------------- src/app/page.tsx | 7 +++-- .../bookclub/components/BookClubMainPage.tsx | 1 - .../bookclub/hooks/useFetchBookClubList.ts | 19 ++++--------- src/lib/utils/fetchBookClubs.ts | 20 ++++++++++++++ 6 files changed, 35 insertions(+), 46 deletions(-) create mode 100644 src/lib/utils/fetchBookClubs.ts diff --git a/src/api/book-club/react-query/queries.ts b/src/api/book-club/react-query/queries.ts index 8b84c6b7..92f84752 100644 --- a/src/api/book-club/react-query/queries.ts +++ b/src/api/book-club/react-query/queries.ts @@ -6,11 +6,8 @@ import { bookClubMainAPI } from '@/api/book-club/bookClubMainAPI'; export const bookClubs = createQueryKeys('bookClubs', { list: (filters?: BookClubParams) => ({ - queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], - queryFn: () => { - console.log('Fetching data with filters:', filters); - bookClubMainAPI.getBookClubs(filters); - }, + queryKey: [{ filters: filters || {} }], + queryFn: () => bookClubMainAPI.getBookClubs(filters), }), detail: (bookClubId: number) => ({ diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 8937fbfc..3693cc9d 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,29 +1,8 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; +import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; export default async function Home() { - console.log('SSR 함수 실행됨'); + const initialData = await fetchBookClubs(); - try { - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (!res.ok) { - throw new Error(`HTTP error! status: ${res.status}`); - } - - const response = await res.json(); - const initialData = response.bookClubs; - - console.log('SSR로 가져온 데이터:', initialData); - - return ; - } catch (error) { - console.error('SSR 데이터 가져오기 실패:', error); - - return ; - } + return ; } diff --git a/src/app/page.tsx b/src/app/page.tsx index 91208bd1..3693cc9d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,8 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; +import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; -export default function Home() { - return ; +export default async function Home() { + const initialData = await fetchBookClubs(); + + return ; } diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index 07f31b3c..6b864202 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -13,7 +13,6 @@ function BookClubMainPage({ initialData }: { initialData: BookClub[] }) { const { clubList, isLoading, isFetching, filters, updateFilters } = useBookClubList({ initialData, - // defaultFilters, }); const router = useRouter(); diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 74d3d60f..57c9e31b 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -3,33 +3,24 @@ import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; -import { bookClubMainAPI } from '@/api/book-club'; const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { const [filters, setFilters] = useState(DEFAULT_FILTERS); const { data, isLoading, isFetching, error } = useQuery({ ...bookClubs.list(filters), - //[데이터 그룹 식별, 세부 데이터 유형, 추가 정보(보통 객체로 전달)] - queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], - queryFn: () => bookClubMainAPI.getBookClubs(filters), - initialData: { bookClubs: initialData }, // SSR로 전달받은 초기 데이터 설정 - staleTime: 0, - // enabled: !!filters, // filters가 존재할 때만 요청 실행 + initialData: { bookClubs: initialData }, + initialDataUpdatedAt: 0, + refetchOnMount: false, + refetchOnWindowFocus: false, }); - const clubList = data?.bookClubs; - console.log(clubList); - - // console.log('queryKey:', ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])]); - // console.log('filters: ',filters); - const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); }; return { - clubList, + clubList: data?.bookClubs, isLoading, isFetching, error, diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts new file mode 100644 index 00000000..51ceb01b --- /dev/null +++ b/src/lib/utils/fetchBookClubs.ts @@ -0,0 +1,20 @@ +export async function fetchBookClubs() { + try { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + throw new Error(`HTTP error! status: ${res.status}`); + } + + const response = await res.json(); + return response.bookClubs; + } catch (error) { + console.error('데이터 가져오기 실패:', error); + return []; + } +} From 07a6a75b05bd40e5356069d2c09a935e8dbe6c95 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 20 Jan 2025 11:48:37 +0900 Subject: [PATCH 09/48] =?UTF-8?q?=F0=9F=92=84[Design]=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=20=EC=B9=B4=EB=93=9C=20size=3D100=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/constants/filters.ts | 2 +- src/lib/utils/fetchBookClubs.ts | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/lib/constants/filters.ts b/src/lib/constants/filters.ts index 0ee948a3..98505373 100644 --- a/src/lib/constants/filters.ts +++ b/src/lib/constants/filters.ts @@ -5,6 +5,6 @@ export const DEFAULT_FILTERS: BookClubParams = { meetingType: 'ALL', order: 'DESC', page: 1, - size: 10, + size: 100, searchKeyword: '', }; diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index 51ceb01b..dafe472e 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -1,11 +1,14 @@ export async function fetchBookClubs() { try { - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, }, - }); + ); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); From 1887f7e5d697f4a8e1becedb6264147c69fc73fa Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 20 Jan 2025 14:28:54 +0900 Subject: [PATCH 10/48] =?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=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClub.test.ts | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/lib/utils/fetchBookClub.test.ts diff --git a/src/lib/utils/fetchBookClub.test.ts b/src/lib/utils/fetchBookClub.test.ts new file mode 100644 index 00000000..f743473c --- /dev/null +++ b/src/lib/utils/fetchBookClub.test.ts @@ -0,0 +1,66 @@ +import { fetchBookClubs } from './fetchBookClubs'; + +describe('fetchBookClub', () => { + beforeEach(() => { + // fetch mocking + global.fetch = jest.fn(); + }); + + afterAll(() => { + // 테스트 후 모킹 초기화 + jest.resetAllMocks(); + }); + + it('요청 성공시 bookCluns를 반환해야 한다', async () => { + // 모의 응답 데이터 + const mockResponse = { + bookClubs: [ + { id: 1, title: 'bookclub 1' }, + { id: 2, title: 'bookclub 2' }, + ], + }; + + // fetch 모킹 + (global.fetch as jest.Mock).mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue(mockResponse), + }); + + const result = await fetchBookClubs(); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith( + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + expect(result).toEqual(mockResponse.bookClubs); + }); + + it('HTTP 에러가 발생하면 빈 배열을 반환해야 한다', async () => { + // fetch를 에러 상태로 모킹 + (global.fetch as jest.Mock).mockResolvedValue({ + ok: false, + status: 500, + }); + + const result = await fetchBookClubs(); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(result).toEqual([]); + }); + + it('fetch 호출 중에 에러가 발생하면 빈 배열을 반환해야 한다', async () => { + // fetch 가 에러를 던지도록 모킹 + (global.fetch as jest.Mock).mockRejectedValue(new Error('Network Error')); + + const result = await fetchBookClubs(); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(result).toEqual([]); + }); +}); From da9f989ddaf409aab9e1181ff1d5d1ae27c2069b Mon Sep 17 00:00:00 2001 From: wynter24 Date: Wed, 22 Jan 2025 22:51:30 +0900 Subject: [PATCH 11/48] =?UTF-8?q?=E2=9C=85[Test]=20useBookClubList=20?= =?UTF-8?q?=ED=9B=85=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(=EC=BF=BC=EB=A6=AC=ED=82=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EA=B2=80=EC=A6=9D)=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest.config.js | 3 + .../hooks/useFetchBookClubList.test.tsx | 90 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/features/bookclub/hooks/useFetchBookClubList.test.tsx diff --git a/jest.config.js b/jest.config.js index 2d756f45..970b1b93 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,9 @@ const config = { coverageProvider: 'v8', testEnvironment: 'jsdom', // setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, }; // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async diff --git a/src/features/bookclub/hooks/useFetchBookClubList.test.tsx b/src/features/bookclub/hooks/useFetchBookClubList.test.tsx new file mode 100644 index 00000000..127dc96f --- /dev/null +++ b/src/features/bookclub/hooks/useFetchBookClubList.test.tsx @@ -0,0 +1,90 @@ +import { bookClubs } from '@/api/book-club/react-query'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, act, waitFor } from '@testing-library/react'; +import useBookClubList from './useFetchBookClubList'; +import { BookClubParams } from '@/types/bookclubs'; +import { mockBookClubs } from '@/mocks/mockDatas'; + +// API 호출 모킹 +jest.mock('@/api/book-club/react-query'); + +describe('useBookClubList', () => { + const queryClient = new QueryClient(); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + + beforeEach(() => { + jest.clearAllMocks(); + queryClient.clear(); + }); + + it('초기 데이터가 올바르게 설정되는지 테스트', () => { + (bookClubs.list as unknown as jest.Mock).mockReturnValue({ + queryKey: ['bookClubs', { filters: {} }], + queryFn: jest.fn().mockResolvedValue({ bookClubs: mockBookClubs }), + }); + + const { result } = renderHook( + () => useBookClubList({ initialData: mockBookClubs }), + { + wrapper, + }, + ); + + expect(result.current.clubList).toEqual(mockBookClubs); + expect(result.current.isLoading).toBe(false); + expect(result.current.isFetching).toBe(false); + }); + + it('필터 업데이트하고 다시 쿼리를 호출하는지 테스트', async () => { + const filters: BookClubParams = { bookClubType: 'FREE' }; + (bookClubs.list as unknown as jest.Mock).mockReturnValue({ + queryKey: ['bookClubs', filters], + queryFn: jest.fn().mockResolvedValue({}), + }); + + const { result } = renderHook( + () => useBookClubList({ initialData: mockBookClubs }), + { + wrapper, + }, + ); + + act(() => { + result.current.updateFilters(filters); + }); + + await waitFor(() => + expect(bookClubs.list).toHaveBeenCalledWith( + expect.objectContaining(filters), + ), + ); + expect(result.current.filters.bookClubType).toBe('FREE'); + }); + + it('API 호출 실패 시 에러 반환', async () => { + const mockError = new Error('Network Error'); + const mockQueryFn = jest.fn().mockRejectedValue(mockError); + + (bookClubs.list as unknown as jest.Mock).mockReturnValue({ + queryKey: ['bookClubs', {}], + queryFn: mockQueryFn, + }); + + const { result } = renderHook(() => useBookClubList({ initialData: [] }), { + wrapper, + }); + + await act(async () => { + await queryClient.prefetchQuery(bookClubs.list()); + }); + + await waitFor(() => { + expect(result.current.error).toBeDefined(); + }); + + expect(result.current.error?.message).toBe('Network Error'); + }); +}); From bc44663c1b961c22e6732fcdda569d2ffc3d68cc Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 13 Jan 2025 11:37:36 +0900 Subject: [PATCH 12/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20fetching=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookclub/page.tsx | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 91208bd1..c322215c 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,5 +1,35 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; -export default function Home() { - return ; +export default async function Home() { + console.log('SSR 함수 실행됨'); + + const DEFAULT_PAGE = 1; + const DEFAULT_SIZE = 10; + + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?page=${DEFAULT_PAGE}&size=${DEFAULT_SIZE}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + if (!res.ok) { + throw new Error(`HTTP error! status: ${res.status}`); + } + + const response = await res.json(); + const initialData = response.bookClubs; + + console.log('SSR로 가져온 데이터:', initialData); + + return ; + } catch (error) { + console.error('SSR 데이터 가져오기 실패:', error); + + return ; + } } From ecd7a988af334f9c2f0da58323d11f4f74bb8331 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 14 Jan 2025 15:02:48 +0900 Subject: [PATCH 13/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20fetching?= =?UTF-8?q?=20=ED=95=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20useQuery?= =?UTF-8?q?=20=EC=B4=88=EA=B8=B0=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/bookclub/hooks/useFetchBookClubList.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 2b70ca45..5537816e 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -1,17 +1,21 @@ import { useState } from 'react'; -import { BookClubParams } from '@/types/bookclubs'; +import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; -const useBookClubList = () => { +const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { + // const [filters, setFilters] = useState(defaultFilters); const [filters, setFilters] = useState(DEFAULT_FILTERS); const { data, isLoading, error } = useQuery({ ...bookClubs.list(filters), + initialData: { bookClubs: initialData }, // SSR로 전달받은 초기 데이터 설정 + staleTime: 1000 * 60 * 5, // 5분 동안 데이터를 Fresh 상태로 유지 }); const clubList = data?.bookClubs; + console.log(clubList); const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); From c37ebec9186d844efd0bbddc97646fec9281c220 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Wed, 15 Jan 2025 10:57:33 +0900 Subject: [PATCH 14/48] =?UTF-8?q?=E2=9C=A8[Feat]=20srr=EB=A1=9C=20data=20f?= =?UTF-8?q?etching=20=ED=9B=84=20csr=EB=A1=9C=20data=20fetching=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=A4=91=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/queries.ts | 7 +++++-- src/app/bookclub/page.tsx | 16 +++++----------- .../bookclub/components/BookClubMainPage.tsx | 11 ++++++++--- .../bookclub/hooks/useFetchBookClubList.ts | 14 +++++++++++--- src/lib/constants/filters.ts | 2 +- src/types/bookclubs.ts | 2 +- 6 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/api/book-club/react-query/queries.ts b/src/api/book-club/react-query/queries.ts index 92f84752..8b84c6b7 100644 --- a/src/api/book-club/react-query/queries.ts +++ b/src/api/book-club/react-query/queries.ts @@ -6,8 +6,11 @@ import { bookClubMainAPI } from '@/api/book-club/bookClubMainAPI'; export const bookClubs = createQueryKeys('bookClubs', { list: (filters?: BookClubParams) => ({ - queryKey: [{ filters: filters || {} }], - queryFn: () => bookClubMainAPI.getBookClubs(filters), + queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], + queryFn: () => { + console.log('Fetching data with filters:', filters); + bookClubMainAPI.getBookClubs(filters); + }, }), detail: (bookClubId: number) => ({ diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index c322215c..8937fbfc 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -3,19 +3,13 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; export default async function Home() { console.log('SSR 함수 실행됨'); - const DEFAULT_PAGE = 1; - const DEFAULT_SIZE = 10; - try { - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?page=${DEFAULT_PAGE}&size=${DEFAULT_SIZE}`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', }, - ); + }); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index f434bbdf..07f31b3c 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -7,9 +7,14 @@ import ClubListSection from './ClubListSection'; import Button from '@/components/button/Button'; import { useRouter } from 'next/navigation'; import Loading from '@/components/loading/Loading'; +import { BookClub } from '@/types/bookclubs'; -function BookClubMainPage() { - const { clubList, isLoading, filters, updateFilters } = useBookClubList(); +function BookClubMainPage({ initialData }: { initialData: BookClub[] }) { + const { clubList, isLoading, isFetching, filters, updateFilters } = + useBookClubList({ + initialData, + // defaultFilters, + }); const router = useRouter(); @@ -43,7 +48,7 @@ function BookClubMainPage() { } /> - {isLoading ? ( + {isLoading || isFetching ? (
diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 5537816e..74d3d60f 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -3,20 +3,27 @@ import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; +import { bookClubMainAPI } from '@/api/book-club'; const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { - // const [filters, setFilters] = useState(defaultFilters); const [filters, setFilters] = useState(DEFAULT_FILTERS); - const { data, isLoading, error } = useQuery({ + const { data, isLoading, isFetching, error } = useQuery({ ...bookClubs.list(filters), + //[데이터 그룹 식별, 세부 데이터 유형, 추가 정보(보통 객체로 전달)] + queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], + queryFn: () => bookClubMainAPI.getBookClubs(filters), initialData: { bookClubs: initialData }, // SSR로 전달받은 초기 데이터 설정 - staleTime: 1000 * 60 * 5, // 5분 동안 데이터를 Fresh 상태로 유지 + staleTime: 0, + // enabled: !!filters, // filters가 존재할 때만 요청 실행 }); const clubList = data?.bookClubs; console.log(clubList); + // console.log('queryKey:', ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])]); + // console.log('filters: ',filters); + const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); }; @@ -24,6 +31,7 @@ const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { return { clubList, isLoading, + isFetching, error, filters, updateFilters, diff --git a/src/lib/constants/filters.ts b/src/lib/constants/filters.ts index 98505373..0ee948a3 100644 --- a/src/lib/constants/filters.ts +++ b/src/lib/constants/filters.ts @@ -5,6 +5,6 @@ export const DEFAULT_FILTERS: BookClubParams = { meetingType: 'ALL', order: 'DESC', page: 1, - size: 100, + size: 10, searchKeyword: '', }; diff --git a/src/types/bookclubs.ts b/src/types/bookclubs.ts index 81b9c090..e043a785 100644 --- a/src/types/bookclubs.ts +++ b/src/types/bookclubs.ts @@ -42,7 +42,7 @@ export interface BookClub { averageScore?: number; - clubStatus: 'pending' | 'confirmed' | 'closed'; // TODO: 내가 만든 모임에서 '모임 완료' 상태 추가 + clubStatus?: 'pending' | 'confirmed' | 'closed'; // TODO: 내가 만든 모임에서 '모임 완료' 상태 추가 isLiked: boolean; isInactive: boolean; isJoined: boolean; From 8b5f007d6da00274354cbdaf180f593bb0cff501 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sat, 18 Jan 2025 15:37:10 +0900 Subject: [PATCH 15/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A8=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=A0=8C=EB=8D=94=EB=A7=81=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/queries.ts | 7 ++--- src/app/bookclub/page.tsx | 27 +++---------------- src/app/page.tsx | 7 +++-- .../bookclub/components/BookClubMainPage.tsx | 1 - .../bookclub/hooks/useFetchBookClubList.ts | 19 ++++--------- src/lib/utils/fetchBookClubs.ts | 20 ++++++++++++++ 6 files changed, 35 insertions(+), 46 deletions(-) create mode 100644 src/lib/utils/fetchBookClubs.ts diff --git a/src/api/book-club/react-query/queries.ts b/src/api/book-club/react-query/queries.ts index 8b84c6b7..92f84752 100644 --- a/src/api/book-club/react-query/queries.ts +++ b/src/api/book-club/react-query/queries.ts @@ -6,11 +6,8 @@ import { bookClubMainAPI } from '@/api/book-club/bookClubMainAPI'; export const bookClubs = createQueryKeys('bookClubs', { list: (filters?: BookClubParams) => ({ - queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], - queryFn: () => { - console.log('Fetching data with filters:', filters); - bookClubMainAPI.getBookClubs(filters); - }, + queryKey: [{ filters: filters || {} }], + queryFn: () => bookClubMainAPI.getBookClubs(filters), }), detail: (bookClubId: number) => ({ diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 8937fbfc..3693cc9d 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,29 +1,8 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; +import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; export default async function Home() { - console.log('SSR 함수 실행됨'); + const initialData = await fetchBookClubs(); - try { - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (!res.ok) { - throw new Error(`HTTP error! status: ${res.status}`); - } - - const response = await res.json(); - const initialData = response.bookClubs; - - console.log('SSR로 가져온 데이터:', initialData); - - return ; - } catch (error) { - console.error('SSR 데이터 가져오기 실패:', error); - - return ; - } + return ; } diff --git a/src/app/page.tsx b/src/app/page.tsx index 91208bd1..3693cc9d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,8 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; +import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; -export default function Home() { - return ; +export default async function Home() { + const initialData = await fetchBookClubs(); + + return ; } diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index 07f31b3c..6b864202 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -13,7 +13,6 @@ function BookClubMainPage({ initialData }: { initialData: BookClub[] }) { const { clubList, isLoading, isFetching, filters, updateFilters } = useBookClubList({ initialData, - // defaultFilters, }); const router = useRouter(); diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 74d3d60f..57c9e31b 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -3,33 +3,24 @@ import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; -import { bookClubMainAPI } from '@/api/book-club'; const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { const [filters, setFilters] = useState(DEFAULT_FILTERS); const { data, isLoading, isFetching, error } = useQuery({ ...bookClubs.list(filters), - //[데이터 그룹 식별, 세부 데이터 유형, 추가 정보(보통 객체로 전달)] - queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], - queryFn: () => bookClubMainAPI.getBookClubs(filters), - initialData: { bookClubs: initialData }, // SSR로 전달받은 초기 데이터 설정 - staleTime: 0, - // enabled: !!filters, // filters가 존재할 때만 요청 실행 + initialData: { bookClubs: initialData }, + initialDataUpdatedAt: 0, + refetchOnMount: false, + refetchOnWindowFocus: false, }); - const clubList = data?.bookClubs; - console.log(clubList); - - // console.log('queryKey:', ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])]); - // console.log('filters: ',filters); - const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); }; return { - clubList, + clubList: data?.bookClubs, isLoading, isFetching, error, diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts new file mode 100644 index 00000000..51ceb01b --- /dev/null +++ b/src/lib/utils/fetchBookClubs.ts @@ -0,0 +1,20 @@ +export async function fetchBookClubs() { + try { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + throw new Error(`HTTP error! status: ${res.status}`); + } + + const response = await res.json(); + return response.bookClubs; + } catch (error) { + console.error('데이터 가져오기 실패:', error); + return []; + } +} From 6c2a3ae135a9e83c522e3ba8428786fc66590e27 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Wed, 15 Jan 2025 10:57:33 +0900 Subject: [PATCH 16/48] =?UTF-8?q?=E2=9C=A8[Feat]=20srr=EB=A1=9C=20data=20f?= =?UTF-8?q?etching=20=ED=9B=84=20csr=EB=A1=9C=20data=20fetching=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=A4=91=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/queries.ts | 7 +++++-- src/features/bookclub/hooks/useFetchBookClubList.ts | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/api/book-club/react-query/queries.ts b/src/api/book-club/react-query/queries.ts index 92f84752..8b84c6b7 100644 --- a/src/api/book-club/react-query/queries.ts +++ b/src/api/book-club/react-query/queries.ts @@ -6,8 +6,11 @@ import { bookClubMainAPI } from '@/api/book-club/bookClubMainAPI'; export const bookClubs = createQueryKeys('bookClubs', { list: (filters?: BookClubParams) => ({ - queryKey: [{ filters: filters || {} }], - queryFn: () => bookClubMainAPI.getBookClubs(filters), + queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], + queryFn: () => { + console.log('Fetching data with filters:', filters); + bookClubMainAPI.getBookClubs(filters); + }, }), detail: (bookClubId: number) => ({ diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 57c9e31b..3c90812d 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -3,6 +3,7 @@ import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; +import { bookClubMainAPI } from '@/api/book-club'; const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { const [filters, setFilters] = useState(DEFAULT_FILTERS); From 27a8cb6f75cc2919805ebedf00595982afbd4db7 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sat, 18 Jan 2025 15:37:10 +0900 Subject: [PATCH 17/48] =?UTF-8?q?=E2=9C=A8[Feat]=20ssr=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A8=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=A0=8C=EB=8D=94=EB=A7=81=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/queries.ts | 7 ++----- src/features/bookclub/hooks/useFetchBookClubList.ts | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/api/book-club/react-query/queries.ts b/src/api/book-club/react-query/queries.ts index 8b84c6b7..92f84752 100644 --- a/src/api/book-club/react-query/queries.ts +++ b/src/api/book-club/react-query/queries.ts @@ -6,11 +6,8 @@ import { bookClubMainAPI } from '@/api/book-club/bookClubMainAPI'; export const bookClubs = createQueryKeys('bookClubs', { list: (filters?: BookClubParams) => ({ - queryKey: ['bookClubs', 'list', ...(filters ? Object.values(filters) : [])], - queryFn: () => { - console.log('Fetching data with filters:', filters); - bookClubMainAPI.getBookClubs(filters); - }, + queryKey: [{ filters: filters || {} }], + queryFn: () => bookClubMainAPI.getBookClubs(filters), }), detail: (bookClubId: number) => ({ diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 3c90812d..57c9e31b 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -3,7 +3,6 @@ import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; -import { bookClubMainAPI } from '@/api/book-club'; const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { const [filters, setFilters] = useState(DEFAULT_FILTERS); From 01575e7df8f504fd654bff1060101ba47b5fae16 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 20 Jan 2025 11:48:37 +0900 Subject: [PATCH 18/48] =?UTF-8?q?=F0=9F=92=84[Design]=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=20=EC=B9=B4=EB=93=9C=20size=3D100=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/constants/filters.ts | 2 +- src/lib/utils/fetchBookClubs.ts | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/lib/constants/filters.ts b/src/lib/constants/filters.ts index 0ee948a3..98505373 100644 --- a/src/lib/constants/filters.ts +++ b/src/lib/constants/filters.ts @@ -5,6 +5,6 @@ export const DEFAULT_FILTERS: BookClubParams = { meetingType: 'ALL', order: 'DESC', page: 1, - size: 10, + size: 100, searchKeyword: '', }; diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index 51ceb01b..dafe472e 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -1,11 +1,14 @@ export async function fetchBookClubs() { try { - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, }, - }); + ); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); From e5455fadbd31a59e4d35d4dc2604a205a41a7cfe Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 20 Jan 2025 14:28:54 +0900 Subject: [PATCH 19/48] =?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=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClub.test.ts | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/lib/utils/fetchBookClub.test.ts diff --git a/src/lib/utils/fetchBookClub.test.ts b/src/lib/utils/fetchBookClub.test.ts new file mode 100644 index 00000000..f743473c --- /dev/null +++ b/src/lib/utils/fetchBookClub.test.ts @@ -0,0 +1,66 @@ +import { fetchBookClubs } from './fetchBookClubs'; + +describe('fetchBookClub', () => { + beforeEach(() => { + // fetch mocking + global.fetch = jest.fn(); + }); + + afterAll(() => { + // 테스트 후 모킹 초기화 + jest.resetAllMocks(); + }); + + it('요청 성공시 bookCluns를 반환해야 한다', async () => { + // 모의 응답 데이터 + const mockResponse = { + bookClubs: [ + { id: 1, title: 'bookclub 1' }, + { id: 2, title: 'bookclub 2' }, + ], + }; + + // fetch 모킹 + (global.fetch as jest.Mock).mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue(mockResponse), + }); + + const result = await fetchBookClubs(); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith( + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + expect(result).toEqual(mockResponse.bookClubs); + }); + + it('HTTP 에러가 발생하면 빈 배열을 반환해야 한다', async () => { + // fetch를 에러 상태로 모킹 + (global.fetch as jest.Mock).mockResolvedValue({ + ok: false, + status: 500, + }); + + const result = await fetchBookClubs(); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(result).toEqual([]); + }); + + it('fetch 호출 중에 에러가 발생하면 빈 배열을 반환해야 한다', async () => { + // fetch 가 에러를 던지도록 모킹 + (global.fetch as jest.Mock).mockRejectedValue(new Error('Network Error')); + + const result = await fetchBookClubs(); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(result).toEqual([]); + }); +}); From 0361c7001cc17b3047c2f693e2ee2ddd4b87dc1c Mon Sep 17 00:00:00 2001 From: wynter24 Date: Wed, 22 Jan 2025 22:51:30 +0900 Subject: [PATCH 20/48] =?UTF-8?q?=E2=9C=85[Test]=20useBookClubList=20?= =?UTF-8?q?=ED=9B=85=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(=EC=BF=BC=EB=A6=AC=ED=82=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EA=B2=80=EC=A6=9D)=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest.config.js | 3 + .../hooks/useFetchBookClubList.test.tsx | 90 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/features/bookclub/hooks/useFetchBookClubList.test.tsx diff --git a/jest.config.js b/jest.config.js index 2d756f45..970b1b93 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,9 @@ const config = { coverageProvider: 'v8', testEnvironment: 'jsdom', // setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, }; // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async diff --git a/src/features/bookclub/hooks/useFetchBookClubList.test.tsx b/src/features/bookclub/hooks/useFetchBookClubList.test.tsx new file mode 100644 index 00000000..127dc96f --- /dev/null +++ b/src/features/bookclub/hooks/useFetchBookClubList.test.tsx @@ -0,0 +1,90 @@ +import { bookClubs } from '@/api/book-club/react-query'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, act, waitFor } from '@testing-library/react'; +import useBookClubList from './useFetchBookClubList'; +import { BookClubParams } from '@/types/bookclubs'; +import { mockBookClubs } from '@/mocks/mockDatas'; + +// API 호출 모킹 +jest.mock('@/api/book-club/react-query'); + +describe('useBookClubList', () => { + const queryClient = new QueryClient(); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + + beforeEach(() => { + jest.clearAllMocks(); + queryClient.clear(); + }); + + it('초기 데이터가 올바르게 설정되는지 테스트', () => { + (bookClubs.list as unknown as jest.Mock).mockReturnValue({ + queryKey: ['bookClubs', { filters: {} }], + queryFn: jest.fn().mockResolvedValue({ bookClubs: mockBookClubs }), + }); + + const { result } = renderHook( + () => useBookClubList({ initialData: mockBookClubs }), + { + wrapper, + }, + ); + + expect(result.current.clubList).toEqual(mockBookClubs); + expect(result.current.isLoading).toBe(false); + expect(result.current.isFetching).toBe(false); + }); + + it('필터 업데이트하고 다시 쿼리를 호출하는지 테스트', async () => { + const filters: BookClubParams = { bookClubType: 'FREE' }; + (bookClubs.list as unknown as jest.Mock).mockReturnValue({ + queryKey: ['bookClubs', filters], + queryFn: jest.fn().mockResolvedValue({}), + }); + + const { result } = renderHook( + () => useBookClubList({ initialData: mockBookClubs }), + { + wrapper, + }, + ); + + act(() => { + result.current.updateFilters(filters); + }); + + await waitFor(() => + expect(bookClubs.list).toHaveBeenCalledWith( + expect.objectContaining(filters), + ), + ); + expect(result.current.filters.bookClubType).toBe('FREE'); + }); + + it('API 호출 실패 시 에러 반환', async () => { + const mockError = new Error('Network Error'); + const mockQueryFn = jest.fn().mockRejectedValue(mockError); + + (bookClubs.list as unknown as jest.Mock).mockReturnValue({ + queryKey: ['bookClubs', {}], + queryFn: mockQueryFn, + }); + + const { result } = renderHook(() => useBookClubList({ initialData: [] }), { + wrapper, + }); + + await act(async () => { + await queryClient.prefetchQuery(bookClubs.list()); + }); + + await waitFor(() => { + expect(result.current.error).toBeDefined(); + }); + + expect(result.current.error?.message).toBe('Network Error'); + }); +}); From 15fc754c7e084354b23c42151c8ffe84c0efa6af Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 24 Jan 2025 10:09:25 +0900 Subject: [PATCH 21/48] =?UTF-8?q?=F0=9F=90=9B[Fix]=20SSR=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=20=EC=84=9C=EB=B2=84=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98(API=5FURL)=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClubs.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index dafe472e..b1a484e0 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -1,14 +1,12 @@ export async function fetchBookClubs() { + const baseUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL; try { - const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, + const res = await fetch(`${baseUrl}/book-clubs?size=100`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', }, - ); + }); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); From f7b9b74f3628a695c7631c9f9bcb99f654829f1b Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 24 Jan 2025 11:21:28 +0900 Subject: [PATCH 22/48] =?UTF-8?q?=F0=9F=92=AC[Comment]=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B6=94=EA=B0=80=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClubs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index b1a484e0..5bbec747 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -15,7 +15,7 @@ export async function fetchBookClubs() { const response = await res.json(); return response.bookClubs; } catch (error) { - console.error('데이터 가져오기 실패:', error); + console.error('데이터 가져오기 실패: ', error); return []; } } From 24cf4e7be823eebfecad0c236d7c1f99b9e810c3 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 24 Jan 2025 11:30:47 +0900 Subject: [PATCH 23/48] =?UTF-8?q?=E2=9C=85[Test]=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=97=90=EB=9F=AC=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClubs.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index 5bbec747..44fa9fa6 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -1,12 +1,15 @@ export async function fetchBookClubs() { - const baseUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL; + // const baseUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL; try { - const res = await fetch(`${baseUrl}/book-clubs?size=100`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', + const res = await fetch( + `https://d3eoy4ym225l85.cloudfront.net/api/v1/book-clubs?size=100`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, }, - }); + ); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); From 6018847707e1e7a67a7120f461104926d607bbe0 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 24 Jan 2025 11:34:36 +0900 Subject: [PATCH 24/48] =?UTF-8?q?=E2=9C=85[Test]=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=97=90=EB=9F=AC=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClub.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/utils/fetchBookClub.test.ts b/src/lib/utils/fetchBookClub.test.ts index f743473c..bf1b41a2 100644 --- a/src/lib/utils/fetchBookClub.test.ts +++ b/src/lib/utils/fetchBookClub.test.ts @@ -30,7 +30,7 @@ describe('fetchBookClub', () => { expect(fetch).toHaveBeenCalledTimes(1); expect(fetch).toHaveBeenCalledWith( - `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, + `https://d3eoy4ym225l85.cloudfront.net/api/v1/book-clubs?size=100`, { method: 'GET', headers: { From c0373275910813ea0e7de48c0125ac1f0948f077 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 24 Jan 2025 11:50:01 +0900 Subject: [PATCH 25/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EB=B3=80=EC=88=98=20=EC=A3=BC=EC=86=8C=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClub.test.ts | 2 +- src/lib/utils/fetchBookClubs.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/utils/fetchBookClub.test.ts b/src/lib/utils/fetchBookClub.test.ts index bf1b41a2..f743473c 100644 --- a/src/lib/utils/fetchBookClub.test.ts +++ b/src/lib/utils/fetchBookClub.test.ts @@ -30,7 +30,7 @@ describe('fetchBookClub', () => { expect(fetch).toHaveBeenCalledTimes(1); expect(fetch).toHaveBeenCalledWith( - `https://d3eoy4ym225l85.cloudfront.net/api/v1/book-clubs?size=100`, + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, { method: 'GET', headers: { diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index 44fa9fa6..dafe472e 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -1,8 +1,7 @@ export async function fetchBookClubs() { - // const baseUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL; try { const res = await fetch( - `https://d3eoy4ym225l85.cloudfront.net/api/v1/book-clubs?size=100`, + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, { method: 'GET', headers: { @@ -18,7 +17,7 @@ export async function fetchBookClubs() { const response = await res.json(); return response.bookClubs; } catch (error) { - console.error('데이터 가져오기 실패: ', error); + console.error('데이터 가져오기 실패:', error); return []; } } From 72f52f7d95c8e6c29a0ae3fe4b6024e5e2076d33 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 24 Jan 2025 14:36:15 +0900 Subject: [PATCH 26/48] =?UTF-8?q?=F0=9F=93=A6[Chore]=20next.config=20env?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/next.config.ts b/next.config.ts index 51ca252f..5eeb3438 100644 --- a/next.config.ts +++ b/next.config.ts @@ -10,6 +10,9 @@ const nextConfig: NextConfig = { instrumentation: { enabled: true, }, + env: { + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + }, }; export default nextConfig; From 3eb1c529f997f06bfa88fa33bd244e5a2c2a15b9 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 24 Jan 2025 17:32:55 +0900 Subject: [PATCH 27/48] =?UTF-8?q?=F0=9F=93=A6[Chore]=20CI=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=84=A4=EC=A0=95:=20NEXT=5FPUBLIC=5FAPI=5FURL=20?= =?UTF-8?q?=EC=A0=84=EB=8B=AC=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 5 +++++ next.config.ts | 8 +++++--- src/app/bookclub/page.tsx | 7 +++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c239d990..e2d73509 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,9 @@ jobs: with: node-version: ${{ matrix.node-version }} + - name: Set environment variables + run: echo "NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}" >> .env + - name: Install dependencies run: npm install @@ -35,6 +38,8 @@ jobs: run: npm test - name: Build Next.js app + env: + NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} run: npm run build - name: Build Storybook diff --git a/next.config.ts b/next.config.ts index 5eeb3438..03ded23d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -10,9 +10,11 @@ const nextConfig: NextConfig = { instrumentation: { enabled: true, }, - env: { - NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, - }, + // env: { + // NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + // }, }; +// console.log('Build-time NEXT_PUBLIC_API_URL:', process.env.NEXT_PUBLIC_API_URL); + export default nextConfig; diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 3693cc9d..b0012414 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,8 +1,15 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; +// export const dynamic = 'force-dynamic'; + export default async function Home() { const initialData = await fetchBookClubs(); + // console.log( + // 'Server-side NEXT_PUBLIC_API_URL:', + // process.env.NEXT_PUBLIC_API_URL, + // ); + // console.log('something'); return ; } From d321aecfc389afed438364c21b30df1df47f15ff Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sat, 25 Jan 2025 11:58:02 +0900 Subject: [PATCH 28/48] =?UTF-8?q?=F0=9F=92=AC[Comment]=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EB=B3=80=EC=88=98=20test=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.ts | 5 ----- src/app/bookclub/page.tsx | 7 ------- 2 files changed, 12 deletions(-) diff --git a/next.config.ts b/next.config.ts index 03ded23d..51ca252f 100644 --- a/next.config.ts +++ b/next.config.ts @@ -10,11 +10,6 @@ const nextConfig: NextConfig = { instrumentation: { enabled: true, }, - // env: { - // NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, - // }, }; -// console.log('Build-time NEXT_PUBLIC_API_URL:', process.env.NEXT_PUBLIC_API_URL); - export default nextConfig; diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index b0012414..3693cc9d 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,15 +1,8 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; -// export const dynamic = 'force-dynamic'; - export default async function Home() { const initialData = await fetchBookClubs(); - // console.log( - // 'Server-side NEXT_PUBLIC_API_URL:', - // process.env.NEXT_PUBLIC_API_URL, - // ); - // console.log('something'); return ; } From 00b888e9fbbe76c17927a1313b0fec1de2d076ac Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sat, 25 Jan 2025 12:40:19 +0900 Subject: [PATCH 29/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20fetchBookC?= =?UTF-8?q?lub=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20mockBookClubs=20=EC=82=AC=EC=9A=A9=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClub.test.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/lib/utils/fetchBookClub.test.ts b/src/lib/utils/fetchBookClub.test.ts index f743473c..c9c3cf33 100644 --- a/src/lib/utils/fetchBookClub.test.ts +++ b/src/lib/utils/fetchBookClub.test.ts @@ -1,3 +1,4 @@ +import { mockBookClubs } from '@/mocks/mockDatas'; import { fetchBookClubs } from './fetchBookClubs'; describe('fetchBookClub', () => { @@ -11,19 +12,11 @@ describe('fetchBookClub', () => { jest.resetAllMocks(); }); - it('요청 성공시 bookCluns를 반환해야 한다', async () => { - // 모의 응답 데이터 - const mockResponse = { - bookClubs: [ - { id: 1, title: 'bookclub 1' }, - { id: 2, title: 'bookclub 2' }, - ], - }; - + it('요청 성공시 bookClubs를 반환해야 한다', async () => { // fetch 모킹 (global.fetch as jest.Mock).mockResolvedValue({ ok: true, - json: jest.fn().mockResolvedValue(mockResponse), + json: jest.fn().mockResolvedValue({ bookClubs: mockBookClubs }), }); const result = await fetchBookClubs(); @@ -38,7 +31,7 @@ describe('fetchBookClub', () => { }, }, ); - expect(result).toEqual(mockResponse.bookClubs); + expect(result).toEqual(mockBookClubs); }); it('HTTP 에러가 발생하면 빈 배열을 반환해야 한다', async () => { From 169e0c58bf4e119f17f4e83e80342108df7f1954 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sat, 25 Jan 2025 12:42:40 +0900 Subject: [PATCH 30/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20fetchBookC?= =?UTF-8?q?lubs=20=EC=97=90=EB=9F=AC=20=EA=B0=9C=EB=B0=9C=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=EB=A7=8C=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/fetchBookClubs.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index dafe472e..a5bf275d 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -17,7 +17,9 @@ export async function fetchBookClubs() { const response = await res.json(); return response.bookClubs; } catch (error) { - console.error('데이터 가져오기 실패:', error); + if (process.env.NODE_ENV === 'development') { + console.error('Error:', error); // 개발 환경에서만 로그 출력 + } return []; } } From 7780462b322420d5cb44abcab42df42d5dfd981b Mon Sep 17 00:00:00 2001 From: wynter24 Date: Sat, 25 Jan 2025 14:38:07 +0900 Subject: [PATCH 31/48] =?UTF-8?q?=F0=9F=94=A5[Remove]=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20feature/bookclub/api=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/bookclub/api/bookclubApi.ts | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 src/features/bookclub/api/bookclubApi.ts diff --git a/src/features/bookclub/api/bookclubApi.ts b/src/features/bookclub/api/bookclubApi.ts deleted file mode 100644 index ac7a7d9a..00000000 --- a/src/features/bookclub/api/bookclubApi.ts +++ /dev/null @@ -1,7 +0,0 @@ -import apiClient from '@/lib/utils/apiClient'; -import { BookClubParams } from '@/types/bookclubs'; - -export const getBookClubs = async (params?: BookClubParams) => { - const response = await apiClient.get('/book-clubs', { params }); - return response.data.bookClubs; -}; From 4d9f35cfb6f3cb11a422c22781588d7e4efd74ef Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 27 Jan 2025 11:30:49 +0900 Subject: [PATCH 32/48] =?UTF-8?q?=F0=9F=94=A5[Remove]=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20club-fine=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EC=82=AD=EC=A0=9C=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/club-find/api/index.ts | 1 - src/features/club-find/components/index.ts | 1 - src/features/club-find/container/index.ts | 1 - src/features/club-find/hooks/index.ts | 1 - src/features/club-find/types/index.ts | 1 - 5 files changed, 5 deletions(-) delete mode 100644 src/features/club-find/api/index.ts delete mode 100644 src/features/club-find/components/index.ts delete mode 100644 src/features/club-find/container/index.ts delete mode 100644 src/features/club-find/hooks/index.ts delete mode 100644 src/features/club-find/types/index.ts diff --git a/src/features/club-find/api/index.ts b/src/features/club-find/api/index.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/src/features/club-find/api/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/src/features/club-find/components/index.ts b/src/features/club-find/components/index.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/src/features/club-find/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/src/features/club-find/container/index.ts b/src/features/club-find/container/index.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/src/features/club-find/container/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/src/features/club-find/hooks/index.ts b/src/features/club-find/hooks/index.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/src/features/club-find/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/src/features/club-find/types/index.ts b/src/features/club-find/types/index.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/src/features/club-find/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; From 4d195f419de875521c53e5ec841e8a2bac4abfe7 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 27 Jan 2025 16:10:37 +0900 Subject: [PATCH 33/48] =?UTF-8?q?=F0=9F=9A=9A[Rename]=20filters.ts=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20src/constants=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/likeOptimisticUpdate.ts | 2 +- src/{lib => }/constants/filters.ts | 0 src/features/bookclub/hooks/useFetchBookClubList.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{lib => }/constants/filters.ts (100%) diff --git a/src/api/book-club/react-query/likeOptimisticUpdate.ts b/src/api/book-club/react-query/likeOptimisticUpdate.ts index 86a0a536..39f07a0a 100644 --- a/src/api/book-club/react-query/likeOptimisticUpdate.ts +++ b/src/api/book-club/react-query/likeOptimisticUpdate.ts @@ -1,7 +1,7 @@ import { QueryClient } from '@tanstack/react-query'; import { BookClub } from '@/types/bookclubs'; import { bookClubs } from './queries'; -import { DEFAULT_FILTERS } from '@/lib/constants/filters'; +import { DEFAULT_FILTERS } from '@/constants/filters'; export const likeOnMutate = async ( queryClient: QueryClient, diff --git a/src/lib/constants/filters.ts b/src/constants/filters.ts similarity index 100% rename from src/lib/constants/filters.ts rename to src/constants/filters.ts diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 57c9e31b..b6d20950 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -2,7 +2,7 @@ import { useState } from 'react'; import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useQuery } from '@tanstack/react-query'; import { bookClubs } from '@/api/book-club/react-query'; -import { DEFAULT_FILTERS } from '@/lib/constants/filters'; +import { DEFAULT_FILTERS } from '@/constants/filters'; const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { const [filters, setFilters] = useState(DEFAULT_FILTERS); From 4f9e5b4bd4e98889967d83c7c6a7736cea18b320 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 27 Jan 2025 16:59:41 +0900 Subject: [PATCH 34/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20useLikeWit?= =?UTF-8?q?hAuthCheck=20=ED=9B=85=EC=9D=84=20=ED=8C=9D=EC=97=85=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B4=80=EB=A6=AC=20=EC=A0=84=EC=9A=A9?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20#33?= =?UTF-8?q?2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club-details/components/HeaderSection.tsx | 13 ++++++--- src/lib/hooks/useLikeWithAuthCheck.ts | 27 +++++-------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/features/club-details/components/HeaderSection.tsx b/src/features/club-details/components/HeaderSection.tsx index c1d43b62..a2645917 100644 --- a/src/features/club-details/components/HeaderSection.tsx +++ b/src/features/club-details/components/HeaderSection.tsx @@ -13,6 +13,7 @@ import { useJoinClub } from '../hooks'; import { useCancelClub, useLeaveClub, + useLikeClub, useLikeWithAuthCheck, useUnLikeClub, } from '@/lib/hooks/index'; @@ -42,9 +43,10 @@ function HeaderSection({ clubInfo, idAsNumber }: HeaderSectionProps) { const { isLikePopUpOpen, likePopUpLabel, - onCheckAuthPopUp, + onShowAuthPopUp, onCloseCheckAuthPopup, } = useLikeWithAuthCheck(); + const { onConfirmLike } = useLikeClub(); const { onConfirmUnLike } = useUnLikeClub(); const { isLoggedIn, checkLoginStatus, user } = useAuthStore(); @@ -72,14 +74,17 @@ function HeaderSection({ clubInfo, idAsNumber }: HeaderSectionProps) { setIsOpen(true); return; } - - handleJoin(clubInfo.id); + !isLoggedIn ? router.replace('/login') : handleJoin(clubInfo.id); }; const handleLikeClub = () => { + if (!isLoggedIn) { + onShowAuthPopUp(); + return; + } clubInfo.isLiked ? onConfirmUnLike(clubInfo.id) - : onCheckAuthPopUp(clubInfo.id); + : onConfirmLike(clubInfo.id); }; const handleLikePopUpConfirm = () => { diff --git a/src/lib/hooks/useLikeWithAuthCheck.ts b/src/lib/hooks/useLikeWithAuthCheck.ts index a0c09298..e8eaca08 100644 --- a/src/lib/hooks/useLikeWithAuthCheck.ts +++ b/src/lib/hooks/useLikeWithAuthCheck.ts @@ -1,27 +1,14 @@ -import { useEffect, useState } from 'react'; -import { useLikeClub } from './useLikeClub'; -import { useAuthStore } from '@/store/authStore'; +import { useState } from 'react'; export const useLikeWithAuthCheck = () => { - const { onConfirmLike } = useLikeClub(); const [isPopUpOpen, setIsPopUpOpen] = useState(false); const [popUpLabel, setPopUpLabel] = useState(''); - const { isLoggedIn, checkLoginStatus } = useAuthStore(); - - useEffect(() => { - checkLoginStatus(); - }, [checkLoginStatus]); - - const onCheckAuthPopUp = (clubId: number) => { - if (isLoggedIn) { - onConfirmLike(clubId); - } else { - setPopUpLabel( - `로그인 후 이용할 수 있어요.\n로그인 페이지로 이동하시겠어요?`, - ); - setIsPopUpOpen(true); - } + const onShowAuthPopUp = () => { + setPopUpLabel( + `로그인 후 이용할 수 있어요.\n로그인 페이지로 이동하시겠어요?`, + ); + setIsPopUpOpen(true); }; const onCloseCheckAuthPopup = () => { @@ -32,7 +19,7 @@ export const useLikeWithAuthCheck = () => { return { isLikePopUpOpen: isPopUpOpen, likePopUpLabel: popUpLabel, - onCheckAuthPopUp, + onShowAuthPopUp, onCloseCheckAuthPopup, }; }; From 48b7c70672656aa5271ae9ed9802c7882fd95908 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Mon, 27 Jan 2025 17:12:29 +0900 Subject: [PATCH 35/48] =?UTF-8?q?=F0=9F=92=84[Design]=20=EB=B9=84=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=83=81=ED=83=9C=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=ED=95=98=EA=B8=B0=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club-details/components/HeaderSection.tsx | 53 ++++++------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/src/features/club-details/components/HeaderSection.tsx b/src/features/club-details/components/HeaderSection.tsx index a2645917..75cb4926 100644 --- a/src/features/club-details/components/HeaderSection.tsx +++ b/src/features/club-details/components/HeaderSection.tsx @@ -8,7 +8,7 @@ import { formatDateForUI, isPastDate } from '@/lib/utils/formatDateForUI'; import { useAuthStore } from '@/store/authStore'; import { BookClub } from '@/types/bookclubs'; import { useRouter } from 'next/navigation'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useJoinClub } from '../hooks'; import { useCancelClub, @@ -24,16 +24,13 @@ interface HeaderSectionProps { } function HeaderSection({ clubInfo, idAsNumber }: HeaderSectionProps) { - const [isOpen, setIsOpen] = useState(false); - const [isMember, setIsMember] = useState<{ - label: string; - isTwoButton: boolean; - handlePopUpConfirm?: () => void; - } | null>(null); - const { handleJoin } = useJoinClub(); - const { popUpState, onCancel, onConfirmCancel, onClosePopUp } = - useCancelClub(); + const { + popUpState: cancelPopUpState, + onCancel, + onConfirmCancel, + onClosePopUp: onCloseCancelPopUp, + } = useCancelClub(); const { leavePopUpState, onCancelParticipation, @@ -65,16 +62,7 @@ function HeaderSection({ clubInfo, idAsNumber }: HeaderSectionProps) { }, [idAsNumber]); const handleJoinClick = () => { - if (!isLoggedIn) { - setIsMember({ - label: '로그인 후 이용해주세요!', - isTwoButton: true, - handlePopUpConfirm: () => router.replace('/login'), - }); - setIsOpen(true); - return; - } - !isLoggedIn ? router.replace('/login') : handleJoin(clubInfo.id); + !isLoggedIn ? router.push('/login') : handleJoin(clubInfo.id); }; const handleLikeClub = () => { @@ -82,9 +70,11 @@ function HeaderSection({ clubInfo, idAsNumber }: HeaderSectionProps) { onShowAuthPopUp(); return; } - clubInfo.isLiked - ? onConfirmUnLike(clubInfo.id) - : onConfirmLike(clubInfo.id); + if (clubInfo.isLiked) { + onConfirmUnLike(clubInfo.id); + } else { + onConfirmLike(clubInfo.id); + } }; const handleLikePopUpConfirm = () => { @@ -130,17 +120,6 @@ function HeaderSection({ clubInfo, idAsNumber }: HeaderSectionProps) { return (
- {isMember && ( - { - setIsOpen(false); - }} - handlePopUpConfirm={isMember.handlePopUpConfirm} - /> - )} {/* 찜하기 */} From bc05a1e9495e2c4622eb36012a2586352f5baae8 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 28 Jan 2025 11:16:49 +0900 Subject: [PATCH 36/48] =?UTF-8?q?=E2=9C=A8[Feat]=20=EB=B9=84=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=9C=A0=EC=A0=80=20=EC=B0=9C=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=95=88=EB=82=B4=EC=B0=BD=20=EB=9D=84=EC=9A=B0?= =?UTF-8?q?=EA=B8=B0=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookclub/components/ClubListSection.tsx | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/features/bookclub/components/ClubListSection.tsx b/src/features/bookclub/components/ClubListSection.tsx index 95b5231e..e5642e8b 100644 --- a/src/features/bookclub/components/ClubListSection.tsx +++ b/src/features/bookclub/components/ClubListSection.tsx @@ -3,11 +3,13 @@ import Card from '@/components/card/Card'; import { formatDateForUI, isPastDate } from '@/lib/utils/formatDateForUI'; import { useRouter } from 'next/navigation'; -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import EmptyState from '@/components/common-layout/EmptyState'; import { clubStatus } from '@/lib/utils/clubUtils'; import { BookClub } from '@/types/bookclubs'; -import { useLikeClub, useUnLikeClub } from '@/lib/hooks'; +import { useLikeClub, useLikeWithAuthCheck, useUnLikeClub } from '@/lib/hooks'; +import { useAuthStore } from '@/store/authStore'; +import PopUp from '@/components/pop-up/PopUp'; interface ClubListSectionProps { bookClubs: BookClub[]; @@ -15,13 +17,36 @@ interface ClubListSectionProps { function ClubListSection({ bookClubs = [] }: ClubListSectionProps) { const router = useRouter(); + const { + isLikePopUpOpen, + likePopUpLabel, + onShowAuthPopUp, + onCloseCheckAuthPopup, + } = useLikeWithAuthCheck(); const { onConfirmUnLike } = useUnLikeClub(); const { onConfirmLike } = useLikeClub(); + const { isLoggedIn, checkLoginStatus } = useAuthStore(); + + useEffect(() => { + checkLoginStatus(); + }, [checkLoginStatus]); const today = useMemo(() => new Date(), []); const handleLikeClub = (isLiked: boolean, id: number) => { - isLiked ? onConfirmUnLike(id) : onConfirmLike(id); + if (!isLoggedIn) { + onShowAuthPopUp(); + return; + } + if (isLiked) { + onConfirmUnLike(id); + } else { + onConfirmLike(id); + } + }; + + const handleLikePopUpConfirm = () => { + router.push('/login'); }; return ( @@ -59,6 +84,14 @@ function ClubListSection({ bookClubs = [] }: ClubListSectionProps) { subtitle="지금 바로 책 모임을 만들어보세요." /> )} + ); } From 2f83891135030b06c0ea5466205ba7e085334760 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Wed, 29 Jan 2025 19:27:39 +0900 Subject: [PATCH 37/48] =?UTF-8?q?=F0=9F=92=84[Design]=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=20=EC=B0=BE=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=9C=A0=EC=A0=80=EA=B0=80=20=EB=A7=8C=EB=93=A0=20?= =?UTF-8?q?=EB=AA=A8=EC=9E=84=20=EC=B0=9C=20UI=20=EC=A0=9C=EA=B1=B0=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/card/Card.tsx | 5 ++++- src/components/card/types/card.ts | 1 + src/components/card/types/clubCard.ts | 3 +++ src/features/bookclub/components/ClubListSection.tsx | 3 ++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index cd5dc547..ee989c89 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -56,6 +56,7 @@ function CardImage({ onLikeClick, className, isPast, + isHost, ...props }: CardImageProps) { return ( @@ -73,7 +74,7 @@ function CardImage({ fill className={twMerge('object-cover', isPast && 'grayscale')} /> - {isLiked !== undefined && ( + {isLiked !== undefined && !isHost && (
@@ -218,6 +219,7 @@ function Card(props: CardProps) { max, isPast, isCanceled, + isHost, // meetingType, bookClubType, clubStatus, @@ -234,6 +236,7 @@ function Card(props: CardProps) { alt={imageAlt} isLiked={isLiked} isPast={isPast} + isHost={isHost} onLikeClick={onLikeClick} /> diff --git a/src/components/card/types/card.ts b/src/components/card/types/card.ts index 1ed7064b..9587d39c 100644 --- a/src/components/card/types/card.ts +++ b/src/components/card/types/card.ts @@ -34,6 +34,7 @@ interface CardImageProps extends ComponentPropsWithoutRef<'div'> { alt?: string; isLiked?: boolean; isPast?: boolean; + isHost?: boolean; onLikeClick?: () => void; } diff --git a/src/components/card/types/clubCard.ts b/src/components/card/types/clubCard.ts index f20afced..dd42604d 100644 --- a/src/components/card/types/clubCard.ts +++ b/src/components/card/types/clubCard.ts @@ -30,6 +30,9 @@ interface DefaultClubCard extends ClubCard { current: number; max: number; + // 호스트 여부 + isHost?: boolean; + //마이페이지 판별 isMyPage?: boolean; } diff --git a/src/features/bookclub/components/ClubListSection.tsx b/src/features/bookclub/components/ClubListSection.tsx index e5642e8b..c682e100 100644 --- a/src/features/bookclub/components/ClubListSection.tsx +++ b/src/features/bookclub/components/ClubListSection.tsx @@ -25,7 +25,7 @@ function ClubListSection({ bookClubs = [] }: ClubListSectionProps) { } = useLikeWithAuthCheck(); const { onConfirmUnLike } = useUnLikeClub(); const { onConfirmLike } = useLikeClub(); - const { isLoggedIn, checkLoginStatus } = useAuthStore(); + const { isLoggedIn, checkLoginStatus, user } = useAuthStore(); useEffect(() => { checkLoginStatus(); @@ -74,6 +74,7 @@ function ClubListSection({ bookClubs = [] }: ClubListSectionProps) { club.endDate, today, )} + isHost={club.hostId === user?.id} onLikeClick={() => handleLikeClub(club.isLiked, club.id)} onClick={() => router.push(`/bookclub/${club.id}`)} /> From 08c72b939d686378327420863ebe18b86129400a Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 4 Feb 2025 11:34:45 +0900 Subject: [PATCH 38/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20initialDat?= =?UTF-8?q?a=20=EB=B0=A9=EC=8B=9D=20=EB=8C=80=EC=8B=A0=20prefetchQuery?= =?UTF-8?q?=EB=A1=9C=20=EC=84=9C=EB=B2=84=EC=97=90=EC=84=9C=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20#30?= =?UTF-8?q?8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookclub/page.tsx | 19 +++++++++++++++++-- src/app/page.tsx | 19 +++++++++++++++++-- .../bookclub/components/BookClubMainPage.tsx | 19 ++++++++++--------- .../bookclub/hooks/useFetchBookClubList.ts | 18 ++---------------- src/lib/utils/fetchBookClubs.ts | 13 +++++++++++-- 5 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 3693cc9d..8e0d78c2 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,8 +1,23 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; +import { DEFAULT_FILTERS } from '@/lib/constants/filters'; import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; +import { + dehydrate, + HydrationBoundary, + QueryClient, +} from '@tanstack/react-query'; export default async function Home() { - const initialData = await fetchBookClubs(); + const queryClient = new QueryClient(); - return ; + await queryClient.prefetchQuery({ + queryKey: ['bookClubs', 'list', DEFAULT_FILTERS], + queryFn: () => fetchBookClubs(DEFAULT_FILTERS), + }); + + return ( + + + + ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 3693cc9d..8e0d78c2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,8 +1,23 @@ import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; +import { DEFAULT_FILTERS } from '@/lib/constants/filters'; import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; +import { + dehydrate, + HydrationBoundary, + QueryClient, +} from '@tanstack/react-query'; export default async function Home() { - const initialData = await fetchBookClubs(); + const queryClient = new QueryClient(); - return ; + await queryClient.prefetchQuery({ + queryKey: ['bookClubs', 'list', DEFAULT_FILTERS], + queryFn: () => fetchBookClubs(DEFAULT_FILTERS), + }); + + return ( + + + + ); } diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index 6b864202..cee9b1f3 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -7,18 +7,19 @@ import ClubListSection from './ClubListSection'; import Button from '@/components/button/Button'; import { useRouter } from 'next/navigation'; import Loading from '@/components/loading/Loading'; -import { BookClub } from '@/types/bookclubs'; +import { useQuery } from '@tanstack/react-query'; +import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; -function BookClubMainPage({ initialData }: { initialData: BookClub[] }) { - const { clubList, isLoading, isFetching, filters, updateFilters } = - useBookClubList({ - initialData, - }); +function BookClubMainPage() { + const { filters, updateFilters } = useBookClubList(); + const { data, isLoading, isFetching } = useQuery({ + queryKey: ['bookClubs', 'list', filters], + queryFn: () => fetchBookClubs(filters), + staleTime: 1000 * 30, + }); const router = useRouter(); - const user = useAuthStore((state) => state.user); - const userName = user?.nickname || '북코'; const handleFilterChange = (newFilter: Partial) => { @@ -52,7 +53,7 @@ function BookClubMainPage({ initialData }: { initialData: BookClub[] }) { ) : ( - + )} ); diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 57c9e31b..aa2940da 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -1,29 +1,15 @@ import { useState } from 'react'; -import { BookClub, BookClubParams } from '@/types/bookclubs'; -import { useQuery } from '@tanstack/react-query'; -import { bookClubs } from '@/api/book-club/react-query'; +import { BookClubParams } from '@/types/bookclubs'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; -const useBookClubList = ({ initialData }: { initialData: BookClub[] }) => { +const useBookClubList = () => { const [filters, setFilters] = useState(DEFAULT_FILTERS); - const { data, isLoading, isFetching, error } = useQuery({ - ...bookClubs.list(filters), - initialData: { bookClubs: initialData }, - initialDataUpdatedAt: 0, - refetchOnMount: false, - refetchOnWindowFocus: false, - }); - const updateFilters = (newFilters: Partial) => { setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); }; return { - clubList: data?.bookClubs, - isLoading, - isFetching, - error, filters, updateFilters, }; diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index a5bf275d..5d42c56d 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -1,7 +1,16 @@ -export async function fetchBookClubs() { +import { BookClubParams } from '@/types/bookclubs'; + +export async function fetchBookClubs(filters: BookClubParams) { try { + // filters 객체를 URLSearchParams로 변환 + const queryParams = new URLSearchParams( + Object.entries(filters) + .filter(([, value]) => value !== undefined && value !== null) // undefined, null 값 제거 + .map(([key, value]) => [key, String(value)]), // 값들을 문자열로 변환 + ).toString(); + const res = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, + `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?${queryParams}`, { method: 'GET', headers: { From 1054f49bf19b60e795e088d4489485848e831001 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 4 Feb 2025 11:42:45 +0900 Subject: [PATCH 39/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20=EC=B0=9C?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=ED=9B=84=20mutate=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=AA=A8=EC=9E=84=20=EB=AA=A9=EB=A1=9D=EC=9D=98=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=ED=82=A4=20=EB=B3=80=EA=B2=BD=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/customHooks.ts | 30 +++++++------- .../react-query/likeOptimisticUpdate.ts | 40 +++++++++---------- .../bookclub/components/ClubListSection.tsx | 11 +++-- src/lib/hooks/useLikeClub.ts | 6 ++- src/lib/hooks/useUnLikeClub.ts | 6 ++- 5 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/api/book-club/react-query/customHooks.ts b/src/api/book-club/react-query/customHooks.ts index b5d49fbc..0e8cd82f 100644 --- a/src/api/book-club/react-query/customHooks.ts +++ b/src/api/book-club/react-query/customHooks.ts @@ -11,6 +11,7 @@ import { import { WriteReviewParams } from '../types'; import { AxiosError } from 'axios'; import { likeOnError, likeOnMutate } from './likeOptimisticUpdate'; +import { BookClubParams } from '@/types/bookclubs'; export function useBookClubCreateMutation() { const queryClient = useQueryClient(); @@ -113,21 +114,22 @@ export function useCancelBookClub() { }); } -export function useLikeBookClub() { +export function useLikeBookClub(filter: BookClubParams) { const queryClient = useQueryClient(); return useMutation, number>({ mutationFn: (id: number) => bookClubLikeAPI.like(id), onMutate: async (id) => { - return likeOnMutate(queryClient, id, true); + return likeOnMutate(queryClient, id, true, filter); }, //TODO: 로직 확인 후 변경 필요 - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: bookClubs._def, - }); - }, + // onSuccess: () => { + // queryClient.invalidateQueries({ + // queryKey: ['bookClubs', 'list', DEFAULT_FILTERS], + // }); + // // console.log(bookClubs._def) + // }, onError: (_error, id, context) => { if (context) { @@ -137,21 +139,21 @@ export function useLikeBookClub() { }); } -export function useUnLikeBookClub() { +export function useUnLikeBookClub(filter: BookClubParams) { const queryClient = useQueryClient(); return useMutation, number>({ mutationFn: (id: number) => bookClubLikeAPI.unlike(id), onMutate: async (id) => { - return likeOnMutate(queryClient, id, false); + return likeOnMutate(queryClient, id, false, filter); }, //TODO: 로직 확인 후 변경 필요 - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: bookClubs._def, - }); - }, + // onSuccess: () => { + // queryClient.invalidateQueries({ + // queryKey: bookClubs._def, + // }); + // }, onError: (_error, id, context) => { if (context) { diff --git a/src/api/book-club/react-query/likeOptimisticUpdate.ts b/src/api/book-club/react-query/likeOptimisticUpdate.ts index 86a0a536..dce81855 100644 --- a/src/api/book-club/react-query/likeOptimisticUpdate.ts +++ b/src/api/book-club/react-query/likeOptimisticUpdate.ts @@ -1,5 +1,5 @@ import { QueryClient } from '@tanstack/react-query'; -import { BookClub } from '@/types/bookclubs'; +import { BookClub, BookClubParams } from '@/types/bookclubs'; import { bookClubs } from './queries'; import { DEFAULT_FILTERS } from '@/lib/constants/filters'; @@ -7,39 +7,39 @@ export const likeOnMutate = async ( queryClient: QueryClient, id: number, isLiked: boolean, + filter?: BookClubParams, ) => { - const listQueryKey = bookClubs.list(DEFAULT_FILTERS).queryKey; + const listQueryKey = ['bookClubs', 'list', filter || DEFAULT_FILTERS]; const detailQueryKey = bookClubs.detail(id).queryKey; - const previousBookClubs = queryClient.getQueryData<{ - bookClubs: BookClub[]; - }>(listQueryKey); + await queryClient.cancelQueries({ queryKey: listQueryKey }); + await queryClient.cancelQueries({ queryKey: detailQueryKey }); + + // console.log('🔍 수정된 listQueryKey:', listQueryKey); + // console.log('🔍 현재 활성화된 모든 쿼리키:', queryClient.getQueriesData({})); + const previousBookClubs = queryClient.getQueryData<{ bookClubs: BookClub[] }>( + listQueryKey, + ); const previousDetail = queryClient.getQueryData(detailQueryKey); - // 목록 캐시 업데이트 + // if (!previousBookClubs) { + // console.warn('⚠️ 캐시된 데이터가 없습니다. queryKey 확인 필요:', listQueryKey); + // queryClient.invalidateQueries({ queryKey: listQueryKey }); + // } + if (previousBookClubs) { - queryClient.setQueryData(listQueryKey, { - ...previousBookClubs, - bookClubs: previousBookClubs.bookClubs.map((club) => + queryClient.setQueryData(listQueryKey, (old: any) => + old?.map((club: BookClub) => club.id === id ? { ...club, isLiked } : club, ), - }); + ); } - // 상세 캐시 업데이트 if (previousDetail) { - queryClient.setQueryData(detailQueryKey, { - ...previousDetail, - isLiked, - }); + queryClient.setQueryData(detailQueryKey, { ...previousDetail, isLiked }); } - //TODO: 로직 확인 후 변경 필요 - queryClient.invalidateQueries({ - queryKey: bookClubs._def, - }); - return { previousBookClubs, previousDetail }; }; diff --git a/src/features/bookclub/components/ClubListSection.tsx b/src/features/bookclub/components/ClubListSection.tsx index 95b5231e..29bcd6ad 100644 --- a/src/features/bookclub/components/ClubListSection.tsx +++ b/src/features/bookclub/components/ClubListSection.tsx @@ -6,20 +6,23 @@ import { useRouter } from 'next/navigation'; import { useMemo } from 'react'; import EmptyState from '@/components/common-layout/EmptyState'; import { clubStatus } from '@/lib/utils/clubUtils'; -import { BookClub } from '@/types/bookclubs'; +import { BookClub, BookClubParams } from '@/types/bookclubs'; import { useLikeClub, useUnLikeClub } from '@/lib/hooks'; interface ClubListSectionProps { bookClubs: BookClub[]; + filter: BookClubParams; } -function ClubListSection({ bookClubs = [] }: ClubListSectionProps) { +function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) { const router = useRouter(); - const { onConfirmUnLike } = useUnLikeClub(); - const { onConfirmLike } = useLikeClub(); + const { onConfirmUnLike } = useUnLikeClub(filter); + const { onConfirmLike } = useLikeClub(filter); const today = useMemo(() => new Date(), []); + // console.log('🔍 ClubListSection 데이터:', bookClubs); + const handleLikeClub = (isLiked: boolean, id: number) => { isLiked ? onConfirmUnLike(id) : onConfirmLike(id); }; diff --git a/src/lib/hooks/useLikeClub.ts b/src/lib/hooks/useLikeClub.ts index 78f4368b..353278ec 100644 --- a/src/lib/hooks/useLikeClub.ts +++ b/src/lib/hooks/useLikeClub.ts @@ -1,9 +1,11 @@ import { useLikeBookClub } from '@/api/book-club/react-query'; import { showToast } from '@/components/toast/toast'; import { TOAST_MESSAGES } from '@/constants/messages/toast'; +import { BookClubParams } from '@/types/bookclubs'; +import { DEFAULT_FILTERS } from '../constants/filters'; -export const useLikeClub = () => { - const { mutate: likeClub } = useLikeBookClub(); +export const useLikeClub = (filter?: BookClubParams) => { + const { mutate: likeClub } = useLikeBookClub(filter || DEFAULT_FILTERS); const onConfirmLike = (selectedClubId: number) => { likeClub(selectedClubId, { diff --git a/src/lib/hooks/useUnLikeClub.ts b/src/lib/hooks/useUnLikeClub.ts index d65678ef..00bbda19 100644 --- a/src/lib/hooks/useUnLikeClub.ts +++ b/src/lib/hooks/useUnLikeClub.ts @@ -1,9 +1,11 @@ import { useUnLikeBookClub } from '@/api/book-club/react-query'; import { showToast } from '@/components/toast/toast'; import { TOAST_MESSAGES } from '@/constants/messages/toast'; +import { BookClubParams } from '@/types/bookclubs'; +import { DEFAULT_FILTERS } from '../constants/filters'; -export const useUnLikeClub = () => { - const { mutate: unLikeClub } = useUnLikeBookClub(); +export const useUnLikeClub = (filter?: BookClubParams) => { + const { mutate: unLikeClub } = useUnLikeBookClub(filter || DEFAULT_FILTERS); const onConfirmUnLike = (selectedClubId: number) => { unLikeClub(selectedClubId, { From 6af325f8c66fa798f05251c1e1bfc764533a3df1 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 4 Feb 2025 12:32:58 +0900 Subject: [PATCH 40/48] =?UTF-8?q?=E2=9C=85[Test]=20fetchBookClubs=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=88=98=EC=A0=95=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hooks/useFetchBookClubList.test.tsx | 92 ++++--------------- src/lib/utils/fetchBookClub.test.ts | 24 ++--- src/lib/utils/fetchBookClubs.ts | 4 +- 3 files changed, 30 insertions(+), 90 deletions(-) diff --git a/src/features/bookclub/hooks/useFetchBookClubList.test.tsx b/src/features/bookclub/hooks/useFetchBookClubList.test.tsx index 127dc96f..9c7d45bb 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.test.tsx +++ b/src/features/bookclub/hooks/useFetchBookClubList.test.tsx @@ -1,90 +1,34 @@ -import { bookClubs } from '@/api/book-club/react-query'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook, act, waitFor } from '@testing-library/react'; -import useBookClubList from './useFetchBookClubList'; -import { BookClubParams } from '@/types/bookclubs'; -import { mockBookClubs } from '@/mocks/mockDatas'; - -// API 호출 모킹 -jest.mock('@/api/book-club/react-query'); +import { renderHook, act } from '@testing-library/react'; +import useBookClubList from '@/features/bookclub/hooks/useFetchBookClubList'; +import { DEFAULT_FILTERS } from '@/lib/constants/filters'; describe('useBookClubList', () => { - const queryClient = new QueryClient(); - - const wrapper = ({ children }: { children: React.ReactNode }) => ( - {children} - ); - - beforeEach(() => { - jest.clearAllMocks(); - queryClient.clear(); - }); - - it('초기 데이터가 올바르게 설정되는지 테스트', () => { - (bookClubs.list as unknown as jest.Mock).mockReturnValue({ - queryKey: ['bookClubs', { filters: {} }], - queryFn: jest.fn().mockResolvedValue({ bookClubs: mockBookClubs }), - }); - - const { result } = renderHook( - () => useBookClubList({ initialData: mockBookClubs }), - { - wrapper, - }, - ); + it('초기 필터 상태는 DEFAULT_FILTERS와 동일해야 한다', () => { + const { result } = renderHook(() => useBookClubList()); - expect(result.current.clubList).toEqual(mockBookClubs); - expect(result.current.isLoading).toBe(false); - expect(result.current.isFetching).toBe(false); + expect(result.current.filters).toEqual(DEFAULT_FILTERS); }); - it('필터 업데이트하고 다시 쿼리를 호출하는지 테스트', async () => { - const filters: BookClubParams = { bookClubType: 'FREE' }; - (bookClubs.list as unknown as jest.Mock).mockReturnValue({ - queryKey: ['bookClubs', filters], - queryFn: jest.fn().mockResolvedValue({}), - }); - - const { result } = renderHook( - () => useBookClubList({ initialData: mockBookClubs }), - { - wrapper, - }, - ); + it('updateFilters를 호출하면 필터 상태가 업데이트되어야 한다', () => { + const { result } = renderHook(() => useBookClubList()); act(() => { - result.current.updateFilters(filters); + result.current.updateFilters({ meetingType: 'ONLINE' }); }); - await waitFor(() => - expect(bookClubs.list).toHaveBeenCalledWith( - expect.objectContaining(filters), - ), - ); - expect(result.current.filters.bookClubType).toBe('FREE'); + expect(result.current.filters.meetingType).toBe('ONLINE'); }); - it('API 호출 실패 시 에러 반환', async () => { - const mockError = new Error('Network Error'); - const mockQueryFn = jest.fn().mockRejectedValue(mockError); - - (bookClubs.list as unknown as jest.Mock).mockReturnValue({ - queryKey: ['bookClubs', {}], - queryFn: mockQueryFn, - }); - - const { result } = renderHook(() => useBookClubList({ initialData: [] }), { - wrapper, - }); - - await act(async () => { - await queryClient.prefetchQuery(bookClubs.list()); - }); + it('updateFilters는 기존 필터 상태를 유지하면서 새로운 필터 값을 반영해야 한다', () => { + const { result } = renderHook(() => useBookClubList()); - await waitFor(() => { - expect(result.current.error).toBeDefined(); + act(() => { + result.current.updateFilters({ location: '서울' }); }); - expect(result.current.error?.message).toBe('Network Error'); + expect(result.current.filters.location).toBe('서울'); + expect(result.current.filters.meetingType).toBe( + DEFAULT_FILTERS.meetingType, + ); }); }); diff --git a/src/lib/utils/fetchBookClub.test.ts b/src/lib/utils/fetchBookClub.test.ts index c9c3cf33..b0f32e0e 100644 --- a/src/lib/utils/fetchBookClub.test.ts +++ b/src/lib/utils/fetchBookClub.test.ts @@ -1,29 +1,27 @@ import { mockBookClubs } from '@/mocks/mockDatas'; import { fetchBookClubs } from './fetchBookClubs'; +import { DEFAULT_FILTERS } from '@/lib/constants/filters'; -describe('fetchBookClub', () => { +describe('fetchBookClubs', () => { beforeEach(() => { - // fetch mocking global.fetch = jest.fn(); }); - afterAll(() => { - // 테스트 후 모킹 초기화 + afterEach(() => { jest.resetAllMocks(); }); - it('요청 성공시 bookClubs를 반환해야 한다', async () => { - // fetch 모킹 + it('요청 성공 시 bookClubs를 반환해야 한다', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: jest.fn().mockResolvedValue({ bookClubs: mockBookClubs }), }); - const result = await fetchBookClubs(); + const result = await fetchBookClubs(DEFAULT_FILTERS); expect(fetch).toHaveBeenCalledTimes(1); expect(fetch).toHaveBeenCalledWith( - `${process.env.NEXT_PUBLIC_API_URL}/book-clubs?size=100`, + expect.stringContaining(`${process.env.NEXT_PUBLIC_API_URL}/book-clubs?`), { method: 'GET', headers: { @@ -34,24 +32,22 @@ describe('fetchBookClub', () => { expect(result).toEqual(mockBookClubs); }); - it('HTTP 에러가 발생하면 빈 배열을 반환해야 한다', async () => { - // fetch를 에러 상태로 모킹 + it('HTTP 에러 발생 시 빈 배열을 반환해야 한다', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: false, status: 500, }); - const result = await fetchBookClubs(); + const result = await fetchBookClubs(DEFAULT_FILTERS); expect(fetch).toHaveBeenCalledTimes(1); expect(result).toEqual([]); }); - it('fetch 호출 중에 에러가 발생하면 빈 배열을 반환해야 한다', async () => { - // fetch 가 에러를 던지도록 모킹 + it('fetch 호출 중 에러 발생 시 빈 배열을 반환해야 한다', async () => { (global.fetch as jest.Mock).mockRejectedValue(new Error('Network Error')); - const result = await fetchBookClubs(); + const result = await fetchBookClubs(DEFAULT_FILTERS); expect(fetch).toHaveBeenCalledTimes(1); expect(result).toEqual([]); diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index 5d42c56d..e19d6c46 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -5,8 +5,8 @@ export async function fetchBookClubs(filters: BookClubParams) { // filters 객체를 URLSearchParams로 변환 const queryParams = new URLSearchParams( Object.entries(filters) - .filter(([, value]) => value !== undefined && value !== null) // undefined, null 값 제거 - .map(([key, value]) => [key, String(value)]), // 값들을 문자열로 변환 + .filter(([, value]) => value !== undefined && value !== null) + .map(([key, value]) => [key, String(value)]), ).toString(); const res = await fetch( From 86fcf7873dfd0dbbc9fef4aa4b6ce2919dee720c Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 4 Feb 2025 14:20:10 +0900 Subject: [PATCH 41/48] =?UTF-8?q?=F0=9F=90=9B[Test]=20Storybook=EC=97=90?= =?UTF-8?q?=EC=84=9C=20useRouter=20Mocking=ED=95=98=EC=97=AC=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/preview.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 4580843a..f3ec658e 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,6 +1,19 @@ import type { Preview } from '@storybook/react'; import '../src/styles/globals.css'; +// useRouter Mocking +jest.mock('next/router', () => ({ + useRouter: () => ({ + push: jest.fn(), + replace: jest.fn(), + back: jest.fn(), + prefetch: jest.fn(), + pathname: '/', + query: {}, + asPath: '/', + }), +})); + const preview: Preview = { parameters: { controls: { From 17a2e67c7a051d7963dfeb003b03cffe4b2064c7 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 4 Feb 2025 14:54:08 +0900 Subject: [PATCH 42/48] =?UTF-8?q?=F0=9F=90=9B[Test]=20Storybook=EC=97=90?= =?UTF-8?q?=EC=84=9C=20useRouter=20Mocking=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=EA=B1=B0=20#308?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/preview.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index f3ec658e..4580843a 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,19 +1,6 @@ import type { Preview } from '@storybook/react'; import '../src/styles/globals.css'; -// useRouter Mocking -jest.mock('next/router', () => ({ - useRouter: () => ({ - push: jest.fn(), - replace: jest.fn(), - back: jest.fn(), - prefetch: jest.fn(), - pathname: '/', - query: {}, - asPath: '/', - }), -})); - const preview: Preview = { parameters: { controls: { From 5f39b9fd5351442a1e99de1b463407636921edcd Mon Sep 17 00:00:00 2001 From: wynter24 Date: Tue, 4 Feb 2025 17:07:24 +0900 Subject: [PATCH 43/48] =?UTF-8?q?=F0=9F=90=9B[Fix]=20merge=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EB=90=9C=20DEFAULT=5FFILTERS=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 8e0d78c2..210d4184 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ +import { DEFAULT_FILTERS } from '@/constants/filters'; import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; -import { DEFAULT_FILTERS } from '@/lib/constants/filters'; import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; import { dehydrate, From 4153bdd90278077aca576e2983d4b7fb703ff280 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Wed, 5 Feb 2025 01:03:11 +0900 Subject: [PATCH 44/48] =?UTF-8?q?=F0=9F=90=9B[Fix]=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EC=97=90=20=EC=B0=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EA=B0=80=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=EB=90=98=EB=8F=84=EB=A1=9D=20prefetch=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9A=94=EC=B2=AD=EC=97=90=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookclub/page.tsx | 13 ++++++++++++- .../bookclub/components/BookClubMainPage.tsx | 2 +- src/lib/utils/fetchBookClubs.ts | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index ca0793f7..69b009a9 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -6,13 +6,24 @@ import { HydrationBoundary, QueryClient, } from '@tanstack/react-query'; +import { cookies } from 'next/headers'; + +export async function getServerSideToken() { + try { + const cookieStore = cookies(); + return (await cookieStore).get('auth_token')?.value; + } catch { + return null; + } +} export default async function Home() { const queryClient = new QueryClient(); + const token = await getServerSideToken(); await queryClient.prefetchQuery({ queryKey: ['bookClubs', 'list', DEFAULT_FILTERS], - queryFn: () => fetchBookClubs(DEFAULT_FILTERS), + queryFn: () => fetchBookClubs(DEFAULT_FILTERS, token || undefined), }); return ( diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index cee9b1f3..c94bab55 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -15,7 +15,7 @@ function BookClubMainPage() { const { data, isLoading, isFetching } = useQuery({ queryKey: ['bookClubs', 'list', filters], queryFn: () => fetchBookClubs(filters), - staleTime: 1000 * 30, + staleTime: 1000 * 60, }); const router = useRouter(); diff --git a/src/lib/utils/fetchBookClubs.ts b/src/lib/utils/fetchBookClubs.ts index e19d6c46..a0dba2e1 100644 --- a/src/lib/utils/fetchBookClubs.ts +++ b/src/lib/utils/fetchBookClubs.ts @@ -1,6 +1,6 @@ import { BookClubParams } from '@/types/bookclubs'; -export async function fetchBookClubs(filters: BookClubParams) { +export async function fetchBookClubs(filters: BookClubParams, token?: string) { try { // filters 객체를 URLSearchParams로 변환 const queryParams = new URLSearchParams( @@ -15,6 +15,7 @@ export async function fetchBookClubs(filters: BookClubParams) { method: 'GET', headers: { 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), }, }, ); From 0b2f48a249360d59f24fdb4594327bbe5225fec2 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Thu, 6 Feb 2025 11:07:53 +0900 Subject: [PATCH 45/48] =?UTF-8?q?=F0=9F=90=9B[Fix]=20=EC=B0=9C=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20hydration=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= =?UTF-8?q?=20=EC=A4=91=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../react-query/likeOptimisticUpdate.ts | 5 +- src/app/layout.tsx | 13 ++-- .../bookclub/components/ClubListSection.tsx | 26 ++++++- .../club-details/components/HeaderSection.tsx | 10 ++- src/lib/contexts/LikeContext.tsx | 78 +++++++++++++++++++ 5 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 src/lib/contexts/LikeContext.tsx diff --git a/src/api/book-club/react-query/likeOptimisticUpdate.ts b/src/api/book-club/react-query/likeOptimisticUpdate.ts index 98b0f24e..59b20988 100644 --- a/src/api/book-club/react-query/likeOptimisticUpdate.ts +++ b/src/api/book-club/react-query/likeOptimisticUpdate.ts @@ -15,8 +15,9 @@ export const likeOnMutate = async ( await queryClient.cancelQueries({ queryKey: listQueryKey }); await queryClient.cancelQueries({ queryKey: detailQueryKey }); - // console.log('🔍 수정된 listQueryKey:', listQueryKey); - // console.log('🔍 현재 활성화된 모든 쿼리키:', queryClient.getQueriesData({})); + console.log('📌 listQueryKey:', listQueryKey); + console.log('📌 detailQueryKey:', detailQueryKey); + console.log('📌 현재 캐시된 쿼리들:', queryClient.getQueriesData({})); const previousBookClubs = queryClient.getQueryData<{ bookClubs: BookClub[] }>( listQueryKey, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 48a6dc59..547bf187 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,6 +6,7 @@ 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', @@ -25,11 +26,13 @@ export default function RootLayout({ strategy="beforeInteractive" /> - -
- - {children} -
+ + +
+ + {children} +
+
diff --git a/src/features/bookclub/components/ClubListSection.tsx b/src/features/bookclub/components/ClubListSection.tsx index d94cb16f..86b6e680 100644 --- a/src/features/bookclub/components/ClubListSection.tsx +++ b/src/features/bookclub/components/ClubListSection.tsx @@ -10,6 +10,8 @@ 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'; +import { useLikeContext } from '@/lib/contexts/LikeContext'; interface ClubListSectionProps { bookClubs: BookClub[]; @@ -27,6 +29,7 @@ function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) { const { onConfirmUnLike } = useUnLikeClub(filter); const { onConfirmLike } = useLikeClub(filter); const { isLoggedIn, checkLoginStatus, user } = useAuthStore(); + const { likedClubs, toggleLike } = useLikeContext(); useEffect(() => { checkLoginStatus(); @@ -36,16 +39,35 @@ function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) { // console.log('🔍 ClubListSection 데이터:', bookClubs); + // ✅ `useState` 사용 대신 `useMemo`를 사용하여 SSR과 CSR의 `isLiked` 상태를 동기화 + const clientBookClubs = useMemo(() => { + // ✅ Hydration 오류 방지: `likedClubs`가 `undefined`일 경우 빈 배열 반환 + if (likedClubs === undefined) return []; + return bookClubs.map((club) => ({ + ...club, + isLiked: likedClubs.has(club.id) ? true : club.isLiked, + })); + }, [bookClubs, likedClubs]); + const handleLikeClub = (isLiked: boolean, id: number) => { if (!isLoggedIn) { onShowAuthPopUp(); return; } + + toggleLike(id, !isLiked); // ✅ 전역 상태 업데이트 + if (isLiked) { onConfirmUnLike(id); } else { onConfirmLike(id); } + + queryClient.setQueryData(['bookClubs', 'list', filter], (oldData: any) => + oldData.map((club: BookClub) => + club.id === id ? { ...club, isLiked: !isLiked } : club, + ), + ); }; const handleLikePopUpConfirm = () => { @@ -54,8 +76,8 @@ function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) { return (
- {bookClubs?.length > 0 ? ( - bookClubs.map((club) => ( + {clientBookClubs?.length > 0 ? ( + clientBookClubs.map((club) => ( | 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()); + } + }, []); + + // ✅ 로그아웃 시 찜한 목록 초기화 + 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 cb8387d209062ccd4499cdb4ed95d4879b3aaa1e Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 7 Feb 2025 09:54:01 +0900 Subject: [PATCH 46/48] =?UTF-8?q?=F0=9F=90=9B[Fix]=20context=20api=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=B0=9C=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20hydration=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= =?UTF-8?q?=20=EC=A4=91=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/book-club/react-query/customHooks.ts | 7 ------ .../react-query/likeOptimisticUpdate.ts | 14 ++++------- .../bookclub/components/BookClubMainPage.tsx | 2 ++ .../bookclub/components/ClubListSection.tsx | 19 +++------------ .../club-details/components/HeaderSection.tsx | 2 +- src/lib/contexts/LikeContext.tsx | 23 ++++++++++++++++++- 6 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/api/book-club/react-query/customHooks.ts b/src/api/book-club/react-query/customHooks.ts index 0e8cd82f..70620140 100644 --- a/src/api/book-club/react-query/customHooks.ts +++ b/src/api/book-club/react-query/customHooks.ts @@ -123,13 +123,6 @@ export function useLikeBookClub(filter: BookClubParams) { onMutate: async (id) => { return likeOnMutate(queryClient, id, true, filter); }, - //TODO: 로직 확인 후 변경 필요 - // onSuccess: () => { - // queryClient.invalidateQueries({ - // queryKey: ['bookClubs', 'list', DEFAULT_FILTERS], - // }); - // // console.log(bookClubs._def) - // }, onError: (_error, id, context) => { if (context) { diff --git a/src/api/book-club/react-query/likeOptimisticUpdate.ts b/src/api/book-club/react-query/likeOptimisticUpdate.ts index 59b20988..5c02550d 100644 --- a/src/api/book-club/react-query/likeOptimisticUpdate.ts +++ b/src/api/book-club/react-query/likeOptimisticUpdate.ts @@ -12,23 +12,17 @@ export const likeOnMutate = async ( const listQueryKey = ['bookClubs', 'list', filter || DEFAULT_FILTERS]; const detailQueryKey = bookClubs.detail(id).queryKey; + // 기존 요청을 취소(데이터 충돌 방지) await queryClient.cancelQueries({ queryKey: listQueryKey }); await queryClient.cancelQueries({ queryKey: detailQueryKey }); - console.log('📌 listQueryKey:', listQueryKey); - console.log('📌 detailQueryKey:', detailQueryKey); - console.log('📌 현재 캐시된 쿼리들:', queryClient.getQueriesData({})); - + // 기존 캐시 데이터 저장 const previousBookClubs = queryClient.getQueryData<{ bookClubs: BookClub[] }>( listQueryKey, ); const previousDetail = queryClient.getQueryData(detailQueryKey); - // if (!previousBookClubs) { - // console.warn('⚠️ 캐시된 데이터가 없습니다. queryKey 확인 필요:', listQueryKey); - // queryClient.invalidateQueries({ queryKey: listQueryKey }); - // } - + // 캐시 데이터 업데이트 if (previousBookClubs) { queryClient.setQueryData(listQueryKey, (old: any) => old?.map((club: BookClub) => @@ -36,11 +30,11 @@ export const likeOnMutate = async ( ), ); } - if (previousDetail) { queryClient.setQueryData(detailQueryKey, { ...previousDetail, isLiked }); } + // API 요청이 실패 시 이전 상태로 복구할 수 있도록 기존 데이터를 반환 return { previousBookClubs, previousDetail }; }; diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index c94bab55..824bfc72 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -15,6 +15,8 @@ function BookClubMainPage() { const { data, isLoading, isFetching } = useQuery({ queryKey: ['bookClubs', 'list', filters], queryFn: () => fetchBookClubs(filters), + enabled: false, // ✅ 서버에서 이미 가져왔기 때문에 클라이언트에서 다시 요청하지 않음 + refetchOnMount: false, // ✅ 마운트 시 다시 데이터를 불러오지 않음 staleTime: 1000 * 60, }); diff --git a/src/features/bookclub/components/ClubListSection.tsx b/src/features/bookclub/components/ClubListSection.tsx index 86b6e680..856c7699 100644 --- a/src/features/bookclub/components/ClubListSection.tsx +++ b/src/features/bookclub/components/ClubListSection.tsx @@ -11,7 +11,6 @@ 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'; -import { useLikeContext } from '@/lib/contexts/LikeContext'; interface ClubListSectionProps { bookClubs: BookClub[]; @@ -29,34 +28,22 @@ function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) { const { onConfirmUnLike } = useUnLikeClub(filter); const { onConfirmLike } = useLikeClub(filter); const { isLoggedIn, checkLoginStatus, user } = useAuthStore(); - const { likedClubs, toggleLike } = useLikeContext(); useEffect(() => { checkLoginStatus(); + console.log('메인 페이지: ', bookClubs); }, [checkLoginStatus]); const today = useMemo(() => new Date(), []); // console.log('🔍 ClubListSection 데이터:', bookClubs); - // ✅ `useState` 사용 대신 `useMemo`를 사용하여 SSR과 CSR의 `isLiked` 상태를 동기화 - const clientBookClubs = useMemo(() => { - // ✅ Hydration 오류 방지: `likedClubs`가 `undefined`일 경우 빈 배열 반환 - if (likedClubs === undefined) return []; - return bookClubs.map((club) => ({ - ...club, - isLiked: likedClubs.has(club.id) ? true : club.isLiked, - })); - }, [bookClubs, likedClubs]); - const handleLikeClub = (isLiked: boolean, id: number) => { if (!isLoggedIn) { onShowAuthPopUp(); return; } - toggleLike(id, !isLiked); // ✅ 전역 상태 업데이트 - if (isLiked) { onConfirmUnLike(id); } else { @@ -76,8 +63,8 @@ function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) { return (
- {clientBookClubs?.length > 0 ? ( - clientBookClubs.map((club) => ( + {bookClubs?.length > 0 ? ( + bookClubs.map((club) => ( boolean; } +// 초깃값과 함께 컨텍스트 생성 const LikeContext = createContext(undefined); export const LikeProvider: React.FC<{ children: React.ReactNode }> = ({ @@ -25,7 +26,7 @@ export const LikeProvider: React.FC<{ children: React.ReactNode }> = ({ ); const { isLoggedIn } = useAuthStore(); - // ✅ localStorage에서 찜한 목록 불러오기 (초기 로드) + // // ✅ localStorage에서 찜한 목록 불러오기 (초기 로드) useEffect(() => { const storedLikes = localStorage.getItem('likedClubs'); if (storedLikes) { @@ -35,6 +36,24 @@ export const LikeProvider: React.FC<{ children: React.ReactNode }> = ({ } }, []); + // ✅ `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) { @@ -62,6 +81,7 @@ export const LikeProvider: React.FC<{ children: React.ReactNode }> = ({ [likedClubs], ); + // 컨텍스트 생서자로 데이터 제공 return ( {children} @@ -70,6 +90,7 @@ export const LikeProvider: React.FC<{ children: React.ReactNode }> = ({ }; export const useLikeContext = () => { + // 컨텍스트 사용으로 데이터 얻기 const context = useContext(LikeContext); if (context === undefined) { throw new Error('useLikeContext must be used within a LikeProvider'); From 663530fcf4cccb8e2e44db44fc38d9db36b04252 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 7 Feb 2025 10:04:42 +0900 Subject: [PATCH 47/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20getServerS?= =?UTF-8?q?ideToken=20uitil=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/bookclub/page.tsx | 11 +---------- src/app/page.tsx | 6 ++++-- src/lib/utils/getServerSideToken.ts | 10 ++++++++++ 3 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 src/lib/utils/getServerSideToken.ts diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 69b009a9..ea8484ec 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -6,16 +6,7 @@ import { HydrationBoundary, QueryClient, } from '@tanstack/react-query'; -import { cookies } from 'next/headers'; - -export async function getServerSideToken() { - try { - const cookieStore = cookies(); - return (await cookieStore).get('auth_token')?.value; - } catch { - return null; - } -} +import { getServerSideToken } from '@/lib/utils/getServerSideToken'; export default async function Home() { const queryClient = new QueryClient(); diff --git a/src/app/page.tsx b/src/app/page.tsx index 210d4184..ea8484ec 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,18 +1,20 @@ -import { DEFAULT_FILTERS } from '@/constants/filters'; import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage'; +import { DEFAULT_FILTERS } from '@/constants/filters'; import { fetchBookClubs } from '@/lib/utils/fetchBookClubs'; import { dehydrate, HydrationBoundary, QueryClient, } from '@tanstack/react-query'; +import { getServerSideToken } from '@/lib/utils/getServerSideToken'; export default async function Home() { const queryClient = new QueryClient(); + const token = await getServerSideToken(); await queryClient.prefetchQuery({ queryKey: ['bookClubs', 'list', DEFAULT_FILTERS], - queryFn: () => fetchBookClubs(DEFAULT_FILTERS), + queryFn: () => fetchBookClubs(DEFAULT_FILTERS, token || undefined), }); return ( diff --git a/src/lib/utils/getServerSideToken.ts b/src/lib/utils/getServerSideToken.ts new file mode 100644 index 00000000..841bb796 --- /dev/null +++ b/src/lib/utils/getServerSideToken.ts @@ -0,0 +1,10 @@ +import { cookies } from 'next/headers'; + +export async function getServerSideToken() { + try { + const cookieStore = cookies(); + return (await cookieStore).get('auth_token')?.value; + } catch { + return null; + } +} From 659362b1fe05febbe3b3eb5e73e84d46087f0533 Mon Sep 17 00:00:00 2001 From: wynter24 Date: Fri, 7 Feb 2025 10:06:06 +0900 Subject: [PATCH 48/48] =?UTF-8?q?=F0=9F=92=84[Design]=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=B4=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20=EC=A4=91=EC=9D=B8=20=EC=B0=9C=20UI=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B2=98=EB=A6=AC=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/card/Card.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index ee989c89..3e1eb04d 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,11 @@ function CardBox({ children, className = '', ...props }: CardBoxProps) { function CardImage({ url, alt = '모임 이미지', - isLiked, - onLikeClick, + // isLiked, + // onLikeClick, className, isPast, - isHost, + // isHost, ...props }: CardImageProps) { return ( @@ -74,11 +74,11 @@ function CardImage({ fill className={twMerge('object-cover', isPast && 'grayscale')} /> - {isLiked !== undefined && !isHost && ( + {/* {isLiked !== undefined && !isHost && (
- )} + )} */} ); }