Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
58426ac
✨[Feat] ssr로 데이터 fetching #308
wynter24 Jan 13, 2025
8522c30
✨[Feat] ssr로 fetching 한 데이터를 useQuery 초기데이터로 설정 #308
wynter24 Jan 14, 2025
cbaa135
✨[Feat] srr로 data fetching 후 csr로 data fetching 작업 중 #308
wynter24 Jan 15, 2025
9b61a24
✨[Feat] ssr로 가져온 데이터로 렌더링 #308
wynter24 Jan 18, 2025
6d785dc
✨[Feat] ssr로 데이터 fetching #308
wynter24 Jan 13, 2025
b90275f
✨[Feat] ssr로 fetching 한 데이터를 useQuery 초기데이터로 설정 #308
wynter24 Jan 14, 2025
f22837a
✨[Feat] srr로 data fetching 후 csr로 data fetching 작업 중 #308
wynter24 Jan 15, 2025
06344da
✨[Feat] ssr로 가져온 데이터로 렌더링 #308
wynter24 Jan 18, 2025
e27baf4
Merge branch '308-refactor-서버-측에서-초기-10개의-데이터를-가져와-렌더링' of https://gi…
wynter24 Jan 18, 2025
07a6a75
💄[Design] 모임 카드 size=100으로 요청 #308
wynter24 Jan 20, 2025
1887f7e
✅[Test] fetchBookClub 테스트 코드 작성 #308
wynter24 Jan 20, 2025
da9f989
✅[Test] useBookClubList 훅 테스트 코드 작성 (쿼리키로 데이터 가져오기 검증) #308
wynter24 Jan 22, 2025
bc44663
✨[Feat] ssr로 데이터 fetching #308
wynter24 Jan 13, 2025
ecd7a98
✨[Feat] ssr로 fetching 한 데이터를 useQuery 초기데이터로 설정 #308
wynter24 Jan 14, 2025
c37ebec
✨[Feat] srr로 data fetching 후 csr로 data fetching 작업 중 #308
wynter24 Jan 15, 2025
8b5f007
✨[Feat] ssr로 가져온 데이터로 렌더링 #308
wynter24 Jan 18, 2025
6c2a3ae
✨[Feat] srr로 data fetching 후 csr로 data fetching 작업 중 #308
wynter24 Jan 15, 2025
27a8cb6
✨[Feat] ssr로 가져온 데이터로 렌더링 #308
wynter24 Jan 18, 2025
01575e7
💄[Design] 모임 카드 size=100으로 요청 #308
wynter24 Jan 20, 2025
e5455fa
✅[Test] fetchBookClub 테스트 코드 작성 #308
wynter24 Jan 20, 2025
0361c70
✅[Test] useBookClubList 훅 테스트 코드 작성 (쿼리키로 데이터 가져오기 검증) #308
wynter24 Jan 22, 2025
54a800f
Merge branch '308-refactor-서버-측에서-초기-10개의-데이터를-가져와-렌더링' of https://gi…
wynter24 Jan 22, 2025
15fc754
🐛[Fix] SSR 환경에서 서버 환경변수(API_URL) 누락 문제 해결 #308
wynter24 Jan 24, 2025
f7b9b74
💬[Comment] 주석 추가 #308
wynter24 Jan 24, 2025
24cf4e7
✅[Test] 환경 변수 에러 확인 #308
wynter24 Jan 24, 2025
6018847
✅[Test] 환경 변수 에러 확인 #308
wynter24 Jan 24, 2025
c037327
♻️[Refactor] 환경 변수 주소로 수정 #308
wynter24 Jan 24, 2025
72f52f7
📦[Chore] next.config env 설정 #308
wynter24 Jan 24, 2025
3eb1c52
📦[Chore] CI 환경설정: NEXT_PUBLIC_API_URL 전달 설정 추가 #308
wynter24 Jan 24, 2025
d321aec
💬[Comment] 환경 변수 test 주석 제거 #308
wynter24 Jan 25, 2025
00b888e
♻️[Refactor] fetchBookClub 테스트 코드 공통 mockBookClubs 사용 #308
wynter24 Jan 25, 2025
169e0c5
♻️[Refactor] fetchBookClubs 에러 개발 환경에서만 로그 출력 #308
wynter24 Jan 25, 2025
7780462
🔥[Remove] 불필요한 feature/bookclub/api 삭제 #308
wynter24 Jan 25, 2025
4d9f35c
🔥[Remove] 사용되지 않는 club-fine 폴더 삭제 #332
wynter24 Jan 27, 2025
4d195f4
🚚[Rename] filters.ts 파일 src/constants로 이동 #332
wynter24 Jan 27, 2025
4f9e5b4
♻️[Refactor] useLikeWithAuthCheck 훅을 팝업 상태 관리 전용으로 리팩터링 #332
wynter24 Jan 27, 2025
48b7c70
💄[Design] 비로그인 상태에서 참여하기 클릭 시 로그인 페이지로 이동 #332
wynter24 Jan 27, 2025
bc05a1e
✨[Feat] 비로그인 유저 찜하기 클릭 시 로그인 안내창 띄우기 #332
wynter24 Jan 28, 2025
2f83891
💄[Design] 모임 찾기 페이지에서 유저가 만든 모임 찜 UI 제거 #332
wynter24 Jan 29, 2025
08c72b9
♻️[Refactor] initialData 방식 대신 prefetchQuery로 서버에서 데이터 가져오기 #308
wynter24 Feb 4, 2025
1054f49
♻️[Refactor] 찜하기 후 mutate되는 모임 목록의 쿼리키 변경 #308
wynter24 Feb 4, 2025
6af325f
✅[Test] fetchBookClubs 함수 수정에 따른 테스트 코드 업데이트 #308
wynter24 Feb 4, 2025
86fcf78
🐛[Test] Storybook에서 useRouter Mocking하여 오류 해결 #308
wynter24 Feb 4, 2025
17a2e67
🐛[Test] Storybook에서 useRouter Mocking 코드 제거거 #308
wynter24 Feb 4, 2025
51e6da7
Merge branch #308 into #332
wynter24 Feb 4, 2025
5f39b9f
🐛[Fix] merge로 변경된 DEFAULT_FILTERS 경로 수정 #332
wynter24 Feb 4, 2025
4153bdd
🐛[Fix] 서버에 찜 데이터가 반영되도록 prefetch 시 요청에 토큰 추가 #332
wynter24 Feb 4, 2025
0b2f48a
🐛[Fix] 찜하기 hydration 문제 해결 중 #332
wynter24 Feb 6, 2025
cb8387d
🐛[Fix] context api 사용하여 찜하기 hydration 문제 해결 중 #332
wynter24 Feb 7, 2025
663530f
♻️[Refactor] getServerSideToken uitil로 분리 #332
wynter24 Feb 7, 2025
659362b
💄[Design] 배포를 위해 오류 해결 중인 찜 UI 주석 처리 #332
wynter24 Feb 7, 2025
027b165
Merge branch '306-refactor-모임-찾기-페이지' of https://github.com/codeit-te…
wynter24 Feb 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/api/book-club/react-query/customHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,8 @@ export function useLikeBookClub(filter: BookClubParams) {
//TODO: 로직 확인 후 변경 필요
// onSuccess: () => {
// queryClient.invalidateQueries({
// queryKey: ['bookClubs', 'list', DEFAULT_FILTERS],
// queryKey: bookClubs._def,
// });
// // console.log(bookClubs._def)
// },

onError: (_error, id, context) => {
Expand Down
15 changes: 5 additions & 10 deletions src/api/book-club/react-query/likeOptimisticUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { QueryClient } from '@tanstack/react-query';
import { BookClub, BookClubParams } 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,
Expand All @@ -12,34 +12,29 @@ 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('🔍 현재 활성화된 모든 쿼리키:', queryClient.getQueriesData({}));

// 기존 캐시 데이터 저장
const previousBookClubs = queryClient.getQueryData<{ bookClubs: BookClub[] }>(
listQueryKey,
);
const previousDetail = queryClient.getQueryData<BookClub>(detailQueryKey);

// if (!previousBookClubs) {
// console.warn('⚠️ 캐시된 데이터가 없습니다. queryKey 확인 필요:', listQueryKey);
// queryClient.invalidateQueries({ queryKey: listQueryKey });
// }

// 캐시 데이터 업데이트
if (previousBookClubs) {
queryClient.setQueryData(listQueryKey, (old: any) =>
old?.map((club: BookClub) =>
club.id === id ? { ...club, isLiked } : club,
),
);
}

if (previousDetail) {
queryClient.setQueryData(detailQueryKey, { ...previousDetail, isLiked });
}

// API 요청이 실패 시 이전 상태로 복구할 수 있도록 기존 데이터를 반환
return { previousBookClubs, previousDetail };
};

Expand Down
6 changes: 4 additions & 2 deletions src/app/bookclub/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage';
import { DEFAULT_FILTERS } from '@/lib/constants/filters';
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 (
Expand Down
13 changes: 8 additions & 5 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -25,11 +26,13 @@ export default function RootLayout({
strategy="beforeInteractive"
/>
<ReactQueryProviders>
<HeaderBar />
<main className="mx-auto flex w-full max-w-[1200px] flex-1 flex-col bg-white">
<Toast />
<MSWComponent>{children}</MSWComponent>
</main>
<LikeProvider>
<HeaderBar />
<main className="mx-auto flex w-full max-w-[1200px] flex-1 flex-col bg-white">
<Toast />
<MSWComponent>{children}</MSWComponent>
</main>
</LikeProvider>
</ReactQueryProviders>
</body>
</html>
Expand Down
6 changes: 4 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import BookClubMainPage from '@/features/bookclub/components/BookClubMainPage';
import { DEFAULT_FILTERS } from '@/lib/constants/filters';
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 (
Expand Down
13 changes: 8 additions & 5 deletions src/components/card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Avatar from '../avatar/Avatar';
import {
LocationIcon,
HostIcon,
HeartIcon,
// HeartIcon,
RatingIcon,
OnlineIcon,
} from '../../../public/icons';
Expand Down Expand Up @@ -52,10 +52,11 @@ function CardBox({ children, className = '', ...props }: CardBoxProps) {
function CardImage({
url,
alt = '모임 이미지',
isLiked,
onLikeClick,
// isLiked,
// onLikeClick,
className,
isPast,
// isHost,
...props
}: CardImageProps) {
return (
Expand All @@ -73,11 +74,11 @@ function CardImage({
fill
className={twMerge('object-cover', isPast && 'grayscale')}
/>
{isLiked !== undefined && (
{/* {isLiked !== undefined && !isHost && (
<div className="absolute right-5 top-[15px] z-10">
<HeartIcon isLiked={isLiked} onClick={onLikeClick} />
</div>
)}
)} */}
</div>
);
}
Expand Down Expand Up @@ -218,6 +219,7 @@ function Card(props: CardProps) {
max,
isPast,
isCanceled,
isHost,
// meetingType,
bookClubType,
clubStatus,
Expand All @@ -234,6 +236,7 @@ function Card(props: CardProps) {
alt={imageAlt}
isLiked={isLiked}
isPast={isPast}
isHost={isHost}
onLikeClick={onLikeClick}
/>

Expand Down
1 change: 1 addition & 0 deletions src/components/card/types/card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface CardImageProps extends ComponentPropsWithoutRef<'div'> {
alt?: string;
isLiked?: boolean;
isPast?: boolean;
isHost?: boolean;
onLikeClick?: () => void;
}

Expand Down
3 changes: 3 additions & 0 deletions src/components/card/types/clubCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ interface DefaultClubCard extends ClubCard {
current: number;
max: number;

// 호스트 여부
isHost?: boolean;

//마이페이지 판별
isMyPage?: boolean;
}
Expand Down
File renamed without changes.
4 changes: 3 additions & 1 deletion src/features/bookclub/components/BookClubMainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ function BookClubMainPage() {
const { data, isLoading, isFetching } = useQuery({
queryKey: ['bookClubs', 'list', filters],
queryFn: () => fetchBookClubs(filters),
staleTime: 1000 * 30,
enabled: false, // ✅ 서버에서 이미 가져왔기 때문에 클라이언트에서 다시 요청하지 않음
refetchOnMount: false, // ✅ 마운트 시 다시 데이터를 불러오지 않음
staleTime: 1000 * 60,
});

const router = useRouter();
Expand Down
49 changes: 46 additions & 3 deletions src/features/bookclub/components/ClubListSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
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, BookClubParams } 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';
import { queryClient } from '@/lib/utils/reactQueryProvider';

interface ClubListSectionProps {
bookClubs: BookClub[];
Expand All @@ -16,15 +19,46 @@ interface ClubListSectionProps {

function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) {
const router = useRouter();
const {
isLikePopUpOpen,
likePopUpLabel,
onShowAuthPopUp,
onCloseCheckAuthPopup,
} = useLikeWithAuthCheck();
const { onConfirmUnLike } = useUnLikeClub(filter);
const { onConfirmLike } = useLikeClub(filter);
const { isLoggedIn, checkLoginStatus, user } = useAuthStore();

useEffect(() => {
checkLoginStatus();
console.log('메인 페이지: ', bookClubs);
}, [checkLoginStatus]);

const today = useMemo(() => new Date(), []);

// console.log('🔍 ClubListSection 데이터:', bookClubs);

const handleLikeClub = (isLiked: boolean, id: number) => {
isLiked ? onConfirmUnLike(id) : onConfirmLike(id);
if (!isLoggedIn) {
onShowAuthPopUp();
return;
}

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 = () => {
router.push('/login');
};

return (
Expand Down Expand Up @@ -52,6 +86,7 @@ function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) {
club.endDate,
today,
)}
isHost={club.hostId === user?.id}
onLikeClick={() => handleLikeClub(club.isLiked, club.id)}
onClick={() => router.push(`/bookclub/${club.id}`)}
/>
Expand All @@ -62,6 +97,14 @@ function ClubListSection({ bookClubs = [], filter }: ClubListSectionProps) {
subtitle="지금 바로 책 모임을 만들어보세요."
/>
)}
<PopUp
isOpen={isLikePopUpOpen}
isLarge={true}
isTwoButton={true}
label={likePopUpLabel}
handlePopUpClose={onCloseCheckAuthPopup}
handlePopUpConfirm={handleLikePopUpConfirm}
/>
</main>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/features/bookclub/hooks/useFetchBookClubList.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { renderHook, act } from '@testing-library/react';
import useBookClubList from '@/features/bookclub/hooks/useFetchBookClubList';
import { DEFAULT_FILTERS } from '@/lib/constants/filters';
import { DEFAULT_FILTERS } from '@/constants/filters';

describe('useBookClubList', () => {
it('초기 필터 상태는 DEFAULT_FILTERS와 동일해야 한다', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/features/bookclub/hooks/useFetchBookClubList.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { BookClubParams } from '@/types/bookclubs';
import { DEFAULT_FILTERS } from '@/lib/constants/filters';
import { DEFAULT_FILTERS } from '@/constants/filters';

const useBookClubList = () => {
const [filters, setFilters] = useState<BookClubParams>(DEFAULT_FILTERS);
Expand Down
Loading