diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 37a89cc..eec532b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -22,7 +22,6 @@ export default function RootLayout({ const historyHeaderList = ["/user/onboarding", "/user/club"]; const clubHeaderList = [ - "/info", "/member/attendance", ]; diff --git a/src/components/molecules/commentList/CommentList.tsx b/src/components/molecules/commentList/CommentList.tsx index fde9bbb..93d084b 100644 --- a/src/components/molecules/commentList/CommentList.tsx +++ b/src/components/molecules/commentList/CommentList.tsx @@ -4,14 +4,17 @@ import { Text } from "@/components/atoms/text"; import useCommentsQuery from "@/hook/comment/useCommentsQuery"; import { CommentsListProps } from "@/types/comment"; import { FormatClubId, FormatPostId } from "@/utils/extractPathElements"; +import { useScrollHandler } from "@/utils/useScrollHandler"; import React, { useImperativeHandle, useState } from "react"; export const CommentList = React.forwardRef( ({ commentCount }: CommentsListProps, ref) => { - const { data: commentList } = useCommentsQuery({ + const { + data: commentList, + fetchNextPage, + hasNextPage, + } = useCommentsQuery({ clubId: FormatClubId(), postId: FormatPostId(), - size: 100, - page: 0, }); const [targetComment, setTargetComment] = useState( undefined @@ -22,9 +25,14 @@ export const CommentList = React.forwardRef( const handleOffTargetComment = () => { setTargetComment(undefined); }; + const handleMoreNextComment = () => { + if (hasNextPage) fetchNextPage(); + }; + const targetRef = useScrollHandler(handleMoreNextComment); useImperativeHandle(ref, () => ({ handleOffTargetComment: () => handleOffTargetComment(), })); //부모 컴포넌트로 토스 + return ( <>
@@ -35,16 +43,21 @@ export const CommentList = React.forwardRef(
- {commentList?.data.content && - commentList.data.content.slice().map((comment, index: number) => ( -
- -
- ))} + {commentList?.pages.flatMap((page) => page.content) && + commentList?.pages + .flatMap((page) => page.content) + .slice() + .map((comment, index: number) => ( +
+ +
+ ))} +
+ {/* 해당을 통해 끝라인 확인 */}
diff --git a/src/components/molecules/headerInfo/HeaderInfo.tsx b/src/components/molecules/headerInfo/HeaderInfo.tsx index 0271756..bf5dcc5 100644 --- a/src/components/molecules/headerInfo/HeaderInfo.tsx +++ b/src/components/molecules/headerInfo/HeaderInfo.tsx @@ -45,7 +45,11 @@ export function HeaderInfo({ }: isHeaderInfo) { const router = useRouter(); const saveData = useSelector((state: RootState) => state.club.children); - const { data: loadData } = useMemberAuthQuery(); + const { data: loadData, isError, error } = useMemberAuthQuery(); + if (isError) { + console.log(error.response?.status); + if (error.response?.status) router.push('/user/onboarding'); + } if (loadData === undefined) return; const clubInfo = saveData.memberId === -1 ? loadData.data : saveData; return ( diff --git a/src/features/board/components/Board.css b/src/features/board/components/Board.css index 9af3d36..b198a52 100644 --- a/src/features/board/components/Board.css +++ b/src/features/board/components/Board.css @@ -1,7 +1,7 @@ /* 5.5rem: bottom에 대한 값 */ .board { padding: 5rem 0; - height: calc(100vh - 5.5rem); + /* height: calc(100vh - 5.5rem); */ display: flex; flex-direction: column; align-items: center; diff --git a/src/features/board/components/Board.tsx b/src/features/board/components/Board.tsx index 8224384..039659d 100644 --- a/src/features/board/components/Board.tsx +++ b/src/features/board/components/Board.tsx @@ -2,6 +2,7 @@ import { Floating } from "@/components/atoms/floating"; import { ICONS } from "@/constants/images"; import usePostListQuery from "@/hook/post/usePostListQuery"; import { FormatPostCategory } from "@/utils/extractPathElements"; +import { useScrollHandler } from "@/utils/useScrollHandler"; import { usePathname, useRouter } from "next/navigation"; import React from "react"; import "./Board.css"; @@ -11,10 +12,8 @@ const Board = () => { const router = useRouter(); const pathname = usePathname(); const category = FormatPostCategory() === "notice" ? "NOTICE" : "FREE"; - const { data } = usePostListQuery({ + const { data, fetchNextPage, hasNextPage } = usePostListQuery({ category: category, - page: 0, - size: 10, }); const inPlusStyle: React.CSSProperties = { width: "1.625rem", @@ -24,25 +23,36 @@ const Board = () => { const handleRouteBoard = () => { router.push(`${pathname}/regis`); }; + const handleMoreContent = () => { + if (hasNextPage) fetchNextPage(); + }; + const targetRef = useScrollHandler(handleMoreContent); return (
-
+
- {data?.data.content.map((item, index) => ( - - ))} + {data?.pages + .flatMap((page) => page.content) + .map((item, index) => ( + + ))} +
+ {/* 해당을 통해 끝라인 확인 */}
{ const { data: noticeList } = usePostListQuery({ category: "NOTICE", - page: 0, size: 7, }); const { data: freeList } = usePostListQuery({ category: "FREE", - page: 0, size: 7, }); return ( @@ -21,12 +19,12 @@ const BoardList = () => { page.content)} /> page.content)} />
); diff --git a/src/features/home/SetHome.tsx b/src/features/home/SetHome.tsx index fce3fd9..83d58a2 100644 --- a/src/features/home/SetHome.tsx +++ b/src/features/home/SetHome.tsx @@ -38,7 +38,7 @@ const SetHome = () => { return ( -
+
diff --git a/src/features/info/ManagerInfo.tsx b/src/features/info/ManagerInfo.tsx index f2ca31e..2499af0 100644 --- a/src/features/info/ManagerInfo.tsx +++ b/src/features/info/ManagerInfo.tsx @@ -7,7 +7,9 @@ export default function ManagerInfo () { return( <> - +
+ +
{ - + console.log(type, selectJoins); + if (type) { + acceptMutate(selectJoins); + } else { + denyMutate(selectJoins); + } } return (
diff --git a/src/features/info/manage/join/components/ManageJoinList.tsx b/src/features/info/manage/join/components/ManageJoinList.tsx index b70f5f5..af75db2 100644 --- a/src/features/info/manage/join/components/ManageJoinList.tsx +++ b/src/features/info/manage/join/components/ManageJoinList.tsx @@ -48,10 +48,10 @@ export default function ManageJoinList({
{ joinList - ?.map((item) => + ?.map((item, idx) => { - const methods = useForm({ - mode: 'onChange' + const { control, handleSubmit, setValue } = + useForm({ + mode: "onChange", + defaultValues: { + memberName: '', + memberIntro: '', + profileImage: '', + isEmailPublic: false, + isPhonePublic: false, + } }); + const [imageUrl, setImageUrl] = useState(); + const handleChangeImage = (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + const selectedFile = files[0]; + const newImageUrl = URL.createObjectURL(selectedFile); + setImageUrl(newImageUrl); + setValue("profileImage", selectedFile); + }; - const { handleSubmit, control } = methods; - + const { mutate } = useMemberProfileMutation() const submitOnboarding = (data: FormData) => { console.log(data); + // mutate(data); } return (
diff --git a/src/hook/club/ClubInfoModifyForm.ts b/src/hook/club/ClubInfoModifyForm.ts new file mode 100644 index 0000000..8ba7ab5 --- /dev/null +++ b/src/hook/club/ClubInfoModifyForm.ts @@ -0,0 +1,19 @@ +// import { MemberProfileUpdata } from "@/types/member"; + +// export const ClubInfoModifyForm = ( +// propData: MemberProfileUpdata, +// isChange: boolean +// ) => { +// const resData = new FormData(); +// resData.append("clubName", propData.clubName); +// resData.append("clubIntro", propData.clubIntro); +// resData.append("contactMeans", propData.contactMeans); +// resData.append("link", propData.link); +// if (isChange) { +// resData.append("ImageIsChanged", String(true)); +// resData.append("profileImage", propData.profileImage); +// } else { +// resData.append("ImageIsChanged", String(false)); +// } +// return resData; +// }; diff --git a/src/hook/club/useClubsInfoMutation.ts b/src/hook/club/useClubsInfoMutation.ts new file mode 100644 index 0000000..501a0e3 --- /dev/null +++ b/src/hook/club/useClubsInfoMutation.ts @@ -0,0 +1,103 @@ +import { http } from "@/apis/http"; +import { ClubModifyFormData } from "@/features/info/club/modify/components/ClubInfoModifyForm"; +import { FormatClubId } from "@/utils/extractPathElements"; +import useToast from "@/utils/useToast"; +import { useMutation } from "@tanstack/react-query"; +import { AxiosError, AxiosResponse } from "axios"; +import { useRouter } from "next/navigation"; + +interface ModifyClubProps { + data: ClubModifyFormData, + imageIsChanged: boolean; +}; + +// 모임 수정 (/clubs) +async function modifyClubs({ + data, + imageIsChanged +}: ModifyClubProps +): Promise> { + const resData = new FormData(); + resData.append( + "request", + new Blob( + [ + JSON.stringify({ + clubName: data.clubName, + clubIntro: data.clubIntro, + contactMeans: data.contactMeans, + link: data.link, + imageIsChanged: imageIsChanged + }), + ], + { type: "application/json" } + ) + ); + if (typeof data.clubProfileImage === "object" && "imageFile" in data.clubProfileImage) + resData.append("profileImage", String(data.clubProfileImage)); + + const config = { + headers: { + accept: "*/*", + "Content-Type": "multipart/form-data", + }, + }; + try { + const clubId = FormatClubId(); + const res = await http.patch>(`/clubs/${clubId}/info`, resData, config); + console.log(res); + if (res.status) { + return res; + } else { + throw new Error("Failed to create club"); + } + } catch (error: any) { + alert(error.response.data.errors[0].message); + console.error("Error creating board:", error); + throw error; + } +} + +interface UseModifyClubs { + mutate: ({ + data, + imageIsChanged + } : ModifyClubProps + ) => void; +} + +// interface ClubResType { +// clubId: number; +// } + +export function useClubsInfoMutation(): UseModifyClubs { + const router = useRouter(); + const { showToast } = useToast(); + + const { mutate } = useMutation, Error, ModifyClubProps>({ + mutationFn: modifyClubs, + onSuccess: (res) => { + const clubId = FormatClubId(); + if (res.status === 200) { + showToast({ + message: "모임 수정이 완료되었습니다.", + type: "success" + }); + console.log(res); + router.push(`/info/${clubId}`); + } + }, + onError: (error) => { + if ((error as AxiosError).isAxiosError) { + const axiosError = error as AxiosError; + if (axiosError.response) { + showToast({ + message: `${axiosError.response.data.message.errors[0].message}`, + type: 'error' + }); + } + } + }, + }); + return { mutate }; +} diff --git a/src/hook/comment/useCommentsQuery.ts b/src/hook/comment/useCommentsQuery.ts index 5140d17..98a20c5 100644 --- a/src/hook/comment/useCommentsQuery.ts +++ b/src/hook/comment/useCommentsQuery.ts @@ -1,28 +1,23 @@ import { http } from "@/apis/http"; import { commentKeys } from "@/constants/keys/postKey"; -import { CommonRes } from "@/types"; +import { CommonPage, CommonRes } from "@/types"; import { Comment } from "@/types/comment"; import { PostDetailProps } from "@/types/post"; -import { useQuery } from "@tanstack/react-query"; +import { useCustomInfiniteQuery } from "@/utils/useCustomInfiniteQuery"; -interface commentListProps extends PostDetailProps { - size: number; - page: number; -} +export const useCommentsQuery = ({ clubId, postId }: PostDetailProps) => { + const size = 10; -export const useCommentsQuery = ({ - clubId, - postId, - size, - page, -}: commentListProps) => { - return useQuery>({ + const getCommentList = async (page: number): Promise> => { + const response = await http.get>( + `/clubs/${clubId}/comment/${postId}?size=${size}&page=${page}` + ); + const data = await response; + return data.data; + }; + return useCustomInfiniteQuery({ queryKey: [commentKeys.comment({ clubId, postId })], - queryFn: async () => - await http.get>( - `/clubs/${clubId}/comment/${postId}?size=${size}&page=${page}` - ), + customQueryFn: getCommentList, }); }; - -export default useCommentsQuery; +export default useCommentsQuery; \ No newline at end of file diff --git a/src/hook/info/useClubJoinAcceptMutation.ts b/src/hook/info/useClubJoinAcceptMutation.ts index f4225b7..4743ada 100644 --- a/src/hook/info/useClubJoinAcceptMutation.ts +++ b/src/hook/info/useClubJoinAcceptMutation.ts @@ -1,19 +1,19 @@ import { http } from "@/apis/http" -import { JoinRequestParam } from "@/types/info" +import { FormatClubId } from "@/utils/extractPathElements" import useToast from "@/utils/useToast" import { useMutation, useQueryClient } from "@tanstack/react-query" import { AxiosError, AxiosResponse } from "axios" // 멤버 가입요청 수락 (/clubs/{clubId}/join/{clubJoinRequestId}/accept) -export const useClubJoinAcceptMutation = ({ - clubId, - clubJoinRequestId -}: JoinRequestParam) => { +export const useClubJoinAcceptMutation = () => { const { showToast } = useToast(); const queryClient = useQueryClient(); + const clubId = FormatClubId(); - return useMutation, Error>({ - mutationFn: () => http.post(`/${clubId}/join/${clubJoinRequestId}/accept`), + return useMutation, Error, number[]>({ + mutationFn: async (selectJoins: number[]) => { + return await http.post(`/clubs/${clubId}/join/accept`, selectJoins) + }, onSuccess: (data) => { console.log(data); if (data.status === 200) { diff --git a/src/hook/info/useClubJoinDenyMutation.ts b/src/hook/info/useClubJoinDenyMutation.ts index ac812a6..f53b6fc 100644 --- a/src/hook/info/useClubJoinDenyMutation.ts +++ b/src/hook/info/useClubJoinDenyMutation.ts @@ -1,5 +1,5 @@ import { http } from "@/apis/http" -import { JoinRequestParam } from "@/types/info" +import { FormatClubId } from "@/utils/extractPathElements" import useToast from "@/utils/useToast" import { useMutation, useQueryClient } from "@tanstack/react-query" import { AxiosError, AxiosResponse } from "axios" @@ -11,15 +11,15 @@ interface ErrorData { } // 멤버 가입요청 거절 (/clubs/{clubId}/join/{clubJoinRequestId}/deny) -export const useClubJoinDenyMutation = ({ - clubId, - clubJoinRequestId -}: JoinRequestParam) => { +export const useClubJoinDenyMutation = () => { const { showToast } = useToast(); const queryClient = useQueryClient(); + const clubId = FormatClubId(); - return useMutation, Error>({ - mutationFn: () => http.post(`/${clubId}/join/${clubJoinRequestId}/deny`), + return useMutation, Error, number[]>({ + mutationFn: async (selectJoins: number[]) => { + return await http.post(`/clubs/${clubId}/join/deny`, selectJoins) + }, onSuccess: (data) => { console.log(data); if (data.status === 200) { diff --git a/src/hook/info/useMyCommentQuery.ts b/src/hook/info/useMyCommentQuery.ts index 2a1d048..332f59f 100644 --- a/src/hook/info/useMyCommentQuery.ts +++ b/src/hook/info/useMyCommentQuery.ts @@ -1,9 +1,8 @@ import { http } from "@/apis/http"; -import { CommonRes } from "@/types"; -import { MyPostProps } from "@/types/info"; -import { PostList } from "@/types/post"; +import { MyCommentWrapperType, MyPostProps } from "@/types/info"; import { FormatClubId } from "@/utils/extractPathElements"; import { useQuery } from "@tanstack/react-query"; +import { AxiosResponse } from "axios"; // 내가 쓴 댓글 export const useMyCommentQuery = ({ @@ -12,11 +11,11 @@ export const useMyCommentQuery = ({ sort="", }: MyPostProps) => { const clubId = FormatClubId(); - const myPostUrl = `/clubs/${clubId}/comment/my?page=${page}&size=${size}`; - return useQuery>({ + const myPostUrl = `/clubs/${clubId}/posts/my-comment-list?page=${page}&size=${size}`; + return useQuery>({ queryKey: ["postMyComment", { clubId, size, page, sort }], queryFn: async () => - await http.get>(myPostUrl) + await http.get>(myPostUrl) }); }; diff --git a/src/hook/member/useMemberAuthQuery.ts b/src/hook/member/useMemberAuthQuery.ts index 6b5aca7..4f85ceb 100644 --- a/src/hook/member/useMemberAuthQuery.ts +++ b/src/hook/member/useMemberAuthQuery.ts @@ -1,15 +1,15 @@ import { http } from "@/apis/http"; -import { CommonNoPageRes } from "@/types"; import { MemberAuthInfo } from "@/types/member"; import { FormatClubId } from "@/utils/extractPathElements"; import { useQuery } from "@tanstack/react-query"; +import { AxiosError, AxiosResponse } from "axios"; // 클럽 auth 조회 export const useMemberAuthQuery = () => { const clubId = FormatClubId(); - return useQuery>({ + return useQuery, AxiosError>({ queryKey: ["myClubAuth", [clubId]], queryFn: async () => { - const response = await http.get>( + const response = await http.get>( `/clubs/${clubId}/member/my/auth` ); return response; diff --git a/src/hook/member/useMemberProfileMutation.ts b/src/hook/member/useMemberProfileMutation.ts new file mode 100644 index 0000000..adf182c --- /dev/null +++ b/src/hook/member/useMemberProfileMutation.ts @@ -0,0 +1,35 @@ +import { http } from "@/apis/http"; +import { FormatClubId } from "@/utils/extractPathElements"; +import useToast from "@/utils/useToast"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useRouter } from "next/navigation"; + +export function useMemberProfileMutation() { + const router = useRouter(); + const { showToast } = useToast(); + const queryClient = useQueryClient(); + const clubId = FormatClubId(); + + return useMutation({ + mutationFn: (data: FormData) => + http.post(`/clubs/${clubId}/member/profile`, data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["clubMemberList", [clubId]], + }); + console.log("프로필 등록 성공"); + showToast({ + message: "프로필 등록 성공", + type: "success", + }); + router.back(); + }, + onError: (error) => { + console.log("프로필 등록 실패", error); + showToast({ + message: "프로필 등록 실패", + type: "error", + }); + }, + }); +} diff --git a/src/hook/post/usePostListQuery.ts b/src/hook/post/usePostListQuery.ts index e988376..de37018 100644 --- a/src/hook/post/usePostListQuery.ts +++ b/src/hook/post/usePostListQuery.ts @@ -1,39 +1,40 @@ "use client"; - import { http } from "@/apis/http"; -import { CommonRes } from "@/types"; +import { CommonPage, CommonRes } from "@/types"; import { PostList } from "@/types/post"; import { FormatClubId } from "@/utils/extractPathElements"; -import { useQuery } from "@tanstack/react-query"; +import { useCustomInfiniteQuery } from "@/utils/useCustomInfiniteQuery"; interface props { keyword?: string; category: "NOTICE" | "FREE"; startData?: string; endData?: string; - page: number; - size: number; + size?: number; sort?: string; } + export const usePostListQuery = ({ keyword = "", category, startData, endData, - page, - size, + size = 10, sort = "string", }: props) => { const clubId = FormatClubId(); - const keywordUrl = `/clubs/${clubId}/posts?keyword=${keyword}&sortBy=createAt&category=${category}&page=${page}&size=${size}`; - const datePullUrl = `/clubs/${clubId}/posts?keyword=${keyword}&startDate=${startData}&endData=${endData}&sortBy=createAt&page=${page}&size=${size}`; - return useQuery>({ - queryKey: ["postList", { clubId, category, size, page, sort }], - queryFn: async () => - await http.get>(keywordUrl, { - headers: { accept: "*/*" }, - }), + const getPostList = async (page: number): Promise> => { + const response = await http.get>( + `/clubs/${clubId}/posts?keyword=${keyword}&sortBy=createAt&category=${category}&page=${page}&size=${size}` + ); + const data = await response; + return data.data; + }; + return useCustomInfiniteQuery({ + queryKey: ["postList", { clubId, category, size, sort }], + customQueryFn: getPostList, }); }; + export default usePostListQuery; diff --git a/src/types/club/types.ts b/src/types/club/types.ts index c7e80e5..ed3dc4d 100644 --- a/src/types/club/types.ts +++ b/src/types/club/types.ts @@ -72,3 +72,13 @@ export interface ClubInfos { link?: string; privateCode?: string; } + +// 클럽 수정 + +export interface ClubModifyFormData { + clubName: string; + clubIntro: string; + contactMeans: string; + link: string; + profileImage?: imageState; +} \ No newline at end of file diff --git a/src/types/info/types.ts b/src/types/info/types.ts index f0fa021..3d2e920 100644 --- a/src/types/info/types.ts +++ b/src/types/info/types.ts @@ -1,3 +1,5 @@ +import { CommonPage } from "../types"; + export interface MyPostProps { clubId?: number; userId?: number; @@ -40,5 +42,44 @@ export interface ClubJoin { export interface JoinRequestParam { clubId: number; - clubJoinRequestId: number; + clubJoinRequestId?: number; +} + +export interface MyCommentWrapperType { + clubMemberImage: string; + memberId: number; + memberName: string; + myCommentList: CommonPage; +} + +export interface MyCommentContent { + comment: MyCommentDetail; + post: MyCommentPost; +} + +export interface MyCommentDetail { + commentId: number; + content: string; + createAt: string; + deleteAt?: string; + parentId?: number; + postId: number; + profileImage: string; + updateAt: string; + writerId: number; + writerName: string; +} + +export interface MyCommentPost { + commentCount: number; + createdAt: string; + isLiked: boolean; + postCategory: "NOTICE" | "FREE"; + postContent: string; + postId: number; + postLikeCount: number; + postTitle: string; + uploadImage?: {}; + writerName: string; + writerProfileImage: string; } \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts index 8635a1e..7369776 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,10 +1,10 @@ export interface Pageable { - pageNumber: number; + pageNumber: number; //현재 페이지 번호 pageSize: number; sort: { - empty: boolean; - sorted: boolean; - unsorted: boolean; + empty: boolean; + sorted: boolean; + unsorted: boolean; }; offset: number; paged: boolean; @@ -16,14 +16,14 @@ export interface CommonPage { content: T[]; pageable: Pageable; last: boolean; - totalPages: number; + totalPages: number; // 전체 페이지 번호 totalElements: number; size: number; number: number; sort: { - empty: boolean; - sorted: boolean; - unsorted: boolean; + empty: boolean; + sorted: boolean; + unsorted: boolean; }; first: boolean; numberOfElements: number; @@ -31,7 +31,7 @@ export interface CommonPage { } export interface CommonRes { - data: CommonPage + data: CommonPage; } export interface CommonNoPageRes { diff --git a/src/utils/useCustomInfiniteQuery.ts b/src/utils/useCustomInfiniteQuery.ts new file mode 100644 index 0000000..3465a55 --- /dev/null +++ b/src/utils/useCustomInfiniteQuery.ts @@ -0,0 +1,21 @@ +import { CommonPage } from "@/types"; +import { useInfiniteQuery } from "@tanstack/react-query"; + +export function useCustomInfiniteQuery({ + queryKey, + customQueryFn, +}: { + queryKey: any; + customQueryFn: (pageParam: number) => Promise>; +}) { + return useInfiniteQuery, Error>({ + queryKey: queryKey, + queryFn: async ({ pageParam = 0 }) => customQueryFn(pageParam as number), + getNextPageParam: (lastPage) => { + return lastPage.last ? undefined : lastPage.pageable.pageNumber + 1; + }, + retry: 3, + refetchOnWindowFocus: false, + initialPageParam: undefined, + }); +} diff --git a/src/utils/useScrollHandler.ts b/src/utils/useScrollHandler.ts new file mode 100644 index 0000000..c98d196 --- /dev/null +++ b/src/utils/useScrollHandler.ts @@ -0,0 +1,61 @@ +import { useEffect, useRef, useState } from "react"; + +// export function useScrollHandler(onScrollEnd: () => void) { +// return useCallback( +// (e: React.UIEvent) => { +// const target = e.currentTarget; +// const isBottom = +// target.scrollHeight - target.scrollTop <= target.clientHeight; +// if (isBottom) { +// console.log("끝"); +// onScrollEnd(); +// } +// }, +// [onScrollEnd] +// ); +// } + +export function useScrollHandler(onScrollEnd: () => void) { + const [isEnd, setIsEnd] = useState(false); + const observer = useRef(null); + const targetRef = useRef(null); + + useEffect(() => { + if (!("IntersectionObserver" in window)) return; // IntersectionObserver 지원 확인용 + + const handleIntersect = ([entry]: IntersectionObserverEntry[]) => { + if (entry.isIntersecting) { + setIsEnd(true); + } + }; + + // IntersectionObserver를 초기화하고 handleIntersect 콜백 연결 + observer.current = new IntersectionObserver(handleIntersect, { + root: null, // viewport를 root로 설정 + rootMargin: "0px", // root와 대상 요소 사이의 여백 설정 + threshold: 1.0, // 요소의 100%가 보일 때 트리거 + }); + + const currentObserver = observer.current; + const targetElement = targetRef.current; + + if (currentObserver && targetElement) { + currentObserver.observe(targetElement); + } + + return () => { + if (currentObserver && targetElement) { + currentObserver.unobserve(targetElement); + } + }; + }, []); + + useEffect(() => { + if (isEnd) { + onScrollEnd(); + setIsEnd(false); + } + }, [isEnd, onScrollEnd]); + + return targetRef; +}