diff --git a/src/components/base-ui/BookStory/bookstory_card.tsx b/src/components/base-ui/BookStory/bookstory_card.tsx index 2e8dbb7..496ede4 100644 --- a/src/components/base-ui/BookStory/bookstory_card.tsx +++ b/src/components/base-ui/BookStory/bookstory_card.tsx @@ -14,6 +14,7 @@ type Props = { commentCount?: number; onSubscribeClick?: () => void; subscribeText?: string; + hideSubscribeButton?: boolean; }; import { formatTimeAgo } from "@/utils/time"; @@ -30,6 +31,7 @@ export default function BookStoryCard({ commentCount = 1, onSubscribeClick, subscribeText = "구독", + hideSubscribeButton = false, }: Props) { return (
- + {!hideSubscribeButton && ( + + )} {/* 2. 책 이미지 (모바일: flex-1 / 데스크탑: h-36) */} diff --git a/src/components/base-ui/Join/JoinButton.tsx b/src/components/base-ui/Join/JoinButton.tsx index f90caa0..ec718ae 100644 --- a/src/components/base-ui/Join/JoinButton.tsx +++ b/src/components/base-ui/Join/JoinButton.tsx @@ -18,8 +18,8 @@ const JoinButton: React.FC = ({ const variants = { primary: disabled ? "bg-[#DADADA] text-[#8D8D8D] cursor-not-allowed" - : "bg-[#7B6154] text-[#FFF] hover:bg-[#7B6154] hover:text-[#FFF]", - secondary: "bg-[#EAE5E2] text-[#5E4A40] border border-[#D2C5B6]", + : "bg-[#7B6154] text-[#FFF] hover:bg-[#6A5246] hover:text-[#FFF] cursor-pointer", + secondary: "bg-[#EAE5E2] text-[#5E4A40] border border-[#D2C5B6] hover:bg-[#D2C5B6] cursor-pointer", }; // Determine classes to avoid conflicts with className prop diff --git a/src/components/base-ui/MyPage/MyBookStoryList.tsx b/src/components/base-ui/MyPage/MyBookStoryList.tsx index 758ef03..43d93d3 100644 --- a/src/components/base-ui/MyPage/MyBookStoryList.tsx +++ b/src/components/base-ui/MyPage/MyBookStoryList.tsx @@ -1,28 +1,67 @@ "use client"; -import React from "react"; +import React, { useEffect } from "react"; import BookStoryCard from "@/components/base-ui/BookStory/bookstory_card"; -import { DUMMY_MY_STORIES } from "@/constants/mocks/mypage"; +import { useMyInfiniteStoriesQuery } from "@/hooks/queries/useStoryQueries"; +import { useInView } from "react-intersection-observer"; const MyBookStoryList = () => { + const { + data, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + isError, + } = useMyInfiniteStoriesQuery(); + + const { ref, inView } = useInView(); + + useEffect(() => { + if (inView && hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]); + + const stories = data?.pages.flatMap((page) => page.basicInfoList) || []; + return (
+ + {isLoading &&

로딩 중...

} + + {!isLoading && isError && ( +

책 이야기를 불러오는 데 실패했습니다.

+ )} + + {!isLoading && !isError && stories.length === 0 && ( +

작성한 책 이야기가 없습니다.

+ )} +
- {DUMMY_MY_STORIES.map((story) => ( + {stories.map((story) => ( ))}
+ + {/* Infinite Scroll Trigger */} +
+ + {isFetchingNextPage && ( +

추가 이야기를 불러오는 중...

+ )}
); }; diff --git a/src/components/base-ui/MyPage/MyMeetingList.tsx b/src/components/base-ui/MyPage/MyMeetingList.tsx index e2cdf63..d126fb4 100644 --- a/src/components/base-ui/MyPage/MyMeetingList.tsx +++ b/src/components/base-ui/MyPage/MyMeetingList.tsx @@ -1,14 +1,43 @@ "use client"; import React from "react"; -import { DUMMY_MEETINGS } from "@/constants/mocks/mypage"; import MyMeetingCard from "./items/MyMeetingCard"; +import { useMyClubsQuery } from "@/hooks/queries/useClubQueries"; const MyMeetingList = () => { + const { data, isLoading, isError } = useMyClubsQuery(); + const clubs = data?.clubList || []; + + if (isLoading) { + return ( +
+ 불러오는 중... +
+ ); + } + + if (isError) { + return ( +
+ 독서 모임을 불러오는 데 실패했습니다. +
+ ); + } + + if (clubs.length === 0) { + return ( +
+

+ 가입한 독서 모임이 없습니다. +

+
+ ); + } + return (
- {DUMMY_MEETINGS.map((meeting) => ( - + {clubs.map((club) => ( + ))}
); diff --git a/src/components/base-ui/MyPage/MyNotificationList.tsx b/src/components/base-ui/MyPage/MyNotificationList.tsx index 391d50b..64e0ee4 100644 --- a/src/components/base-ui/MyPage/MyNotificationList.tsx +++ b/src/components/base-ui/MyPage/MyNotificationList.tsx @@ -1,15 +1,62 @@ "use client"; -import React from "react"; -import { DUMMY_NOTIFICATIONS } from "@/constants/mocks/mypage"; +import React, { useEffect, useMemo } from "react"; import MyNotificationItem from "./items/MyNotificationItem"; +import { useInfiniteNotificationsQuery } from "@/hooks/queries/useNotificationQueries"; +import { useInView } from "react-intersection-observer"; const MyNotificationList = () => { + const { ref, inView } = useInView(); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError } = + useInfiniteNotificationsQuery(); + + const notifications = useMemo(() => { + return data?.pages.flatMap((page) => page.notifications) || []; + }, [data]); + + useEffect(() => { + if (inView && hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]); + + if (isLoading) { + return ( +
+ 불러오는 중... +
+ ); + } + + if (isError) { + return ( +
+ 알림을 불러오는 데 실패했습니다. +
+ ); + } + + if (notifications.length === 0) { + return ( +
+

+ 새로운 알림이 없습니다. +

+
+ ); + } + return (
- {DUMMY_NOTIFICATIONS.map((notification) => ( - + {notifications.map((notification) => ( + ))} +
+ {isFetchingNextPage && ( +
+ 추가 알림을 불러오는 중... +
+ )}
); }; diff --git a/src/components/base-ui/MyPage/UserProfile.tsx b/src/components/base-ui/MyPage/UserProfile.tsx index 8fdce77..df7454e 100644 --- a/src/components/base-ui/MyPage/UserProfile.tsx +++ b/src/components/base-ui/MyPage/UserProfile.tsx @@ -1,20 +1,22 @@ import React from "react"; import Image from "next/image"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import JoinButton from "@/components/base-ui/Join/JoinButton"; import { DUMMY_USER_PROFILE } from "@/constants/mocks/mypage"; -import { useAuthStore } from "@/store/useAuthStore"; +import { useProfileQuery } from "@/hooks/queries/useMemberQueries"; import FloatingFab from "../Float"; const UserProfile = () => { - const { user: authUser } = useAuthStore(); + const router = useRouter(); + const { data: profileData, isLoading, isError } = useProfileQuery(); // 서버 데이터가 있으면 사용하고, 없으면 더미 데이터 사용 (구독자 수 등은 현재 API에 없음) const user = { ...DUMMY_USER_PROFILE, - name: authUser?.nickname || authUser?.email || DUMMY_USER_PROFILE.name, - intro: authUser?.description || DUMMY_USER_PROFILE.intro, - profileImage: authUser?.profileImageUrl || DUMMY_USER_PROFILE.profileImage, + name: profileData?.nickname || DUMMY_USER_PROFILE.name, + intro: profileData?.description || DUMMY_USER_PROFILE.intro, + profileImage: profileData?.profileImageUrl || DUMMY_USER_PROFILE.profileImage, }; return ( @@ -104,18 +106,21 @@ const UserProfile = () => {
- {/* Action Buttons */}
- + router.push("/stories/new")} + className="w-[160px] h-[32px] md:w-[355px] md:h-[48px] p-[12px_16px] gap-[10px] rounded-[8px] font-sans text-[14px] font-semibold md:text-[18px] md:font-medium leading-[135%]" + > 내 책 이야기 쓰기 - + 소식 문의하기
router.push("/stories/new")} /> diff --git a/src/components/base-ui/MyPage/items/MyMeetingCard.tsx b/src/components/base-ui/MyPage/items/MyMeetingCard.tsx index 0f5c99c..adb6de6 100644 --- a/src/components/base-ui/MyPage/items/MyMeetingCard.tsx +++ b/src/components/base-ui/MyPage/items/MyMeetingCard.tsx @@ -2,17 +2,17 @@ import React from "react"; import Image from "next/image"; -import { MyPageMeeting } from "@/types/mypage"; +import { MyClubInfo } from "@/types/club"; interface MyMeetingCardProps { - meeting: MyPageMeeting; + club: MyClubInfo; } -const MyMeetingCard = ({ meeting }: MyMeetingCardProps) => { +const MyMeetingCard = ({ club }: MyMeetingCardProps) => { return (
- {meeting.title} + {club.clubName}