diff --git a/src/app/(pages)/albaTalk/[albatalkId]/page.tsx b/src/app/(pages)/albaTalk/[albatalkId]/page.tsx index a1687ca5..898671d3 100644 --- a/src/app/(pages)/albaTalk/[albatalkId]/page.tsx +++ b/src/app/(pages)/albaTalk/[albatalkId]/page.tsx @@ -7,14 +7,10 @@ import { CommentsSection } from "./sections/CommentsSection"; export default function PostDetailPage() { const { albatalkId } = useParams(); - // 에러 처리 - return ( -
-
- - -
-
+
+ + +
); } diff --git a/src/app/(pages)/albaTalk/[albatalkId]/sections/CommentsSection.tsx b/src/app/(pages)/albaTalk/[albatalkId]/sections/CommentsSection.tsx index 7e7259c2..4e69f3f4 100644 --- a/src/app/(pages)/albaTalk/[albatalkId]/sections/CommentsSection.tsx +++ b/src/app/(pages)/albaTalk/[albatalkId]/sections/CommentsSection.tsx @@ -8,14 +8,15 @@ import Button from "@/app/components/button/default/Button"; import { useAddComment } from "@/hooks/queries/post/comment/useAddComment"; import { useUser } from "@/hooks/queries/user/me/useUser"; import { useComments } from "@/hooks/queries/post/comment/useComments"; -import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner"; import Pagination from "@/app/components/pagination/Pagination"; +import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner"; +import DotLoadingSpinner from "@/app/components/loading-spinner/DotLoadingSpinner"; interface CommentsSectionProps { postId: string; } -export function CommentsSection({ postId }: CommentsSectionProps) { +export function CommentsSection({ postId }: CommentsSectionProps): JSX.Element { const [currentPage, setCurrentPage] = useState(1); const [newComment, setNewComment] = useState(""); @@ -42,10 +43,13 @@ export function CommentsSection({ postId }: CommentsSectionProps) { } }, [addComment, newComment]); - const handlePageChange = (page: number) => { + const handlePageChange = useCallback((page: number) => { setCurrentPage(page); - window.scrollTo({ top: 0, behavior: "smooth" }); - }; + const commentSection = document.querySelector("#comments-section"); + if (commentSection) { + commentSection.scrollIntoView({ behavior: "smooth" }); + } + }, []); if (isCommentsLoading) { return ( @@ -60,59 +64,61 @@ export function CommentsSection({ postId }: CommentsSectionProps) { } return ( -
-

- 댓글({commentsData?.totalItemCount || 0}) -

- -
+
+
+ 댓글({commentsData?.totalItemCount ?? 0}) +
- {/* 댓글 입력 영역 */} -
+
setNewComment(e.target.value)} - className="mb-4 h-[132px] w-full lg:h-[160px]" + className="h-[90px] w-[344px] resize-none rounded-lg bg-background-200 p-4 md:w-[742px] lg:h-[100px] lg:w-[994px] xl:w-[1410px]" + size="h-[120px] w-[375px] md:w-[768px] lg:w-[1024px] xl:w-[1440px] lg:h-[144px]" + variant="white" /> -
- -
+
+
+
- {/* 댓글 목록 */} - {commentsData?.data && commentsData.data.length > 0 ? ( -
- {commentsData.data.map((comment) => ( -
- -
- ))} -
- ) : ( -
- No comments -
- )} +
+ {commentsData?.data?.length ? ( + <> + {commentsData.data.map((comment) => ( +
+ +
+ ))} + + ) : ( +
+ No comments +
+ )} +
- {/* 페이지네이션 */} - {commentsData?.totalPages && commentsData.totalPages > 0 && ( -
- + {(commentsData?.totalPages ?? 0) > 1 && ( +
+
)}
diff --git a/src/app/(pages)/albaTalk/[albatalkId]/sections/PostDetailSection.tsx b/src/app/(pages)/albaTalk/[albatalkId]/sections/PostDetailSection.tsx index 9e19bb84..3b8c411d 100644 --- a/src/app/(pages)/albaTalk/[albatalkId]/sections/PostDetailSection.tsx +++ b/src/app/(pages)/albaTalk/[albatalkId]/sections/PostDetailSection.tsx @@ -9,10 +9,12 @@ import KebabDropdown from "@/app/components/button/dropdown/KebabDropdown"; import useModalStore from "@/store/modalStore"; import useWidth from "@/hooks/useWidth"; import { usePostDetail } from "@/hooks/queries/post/usePostDetail"; +import { useRouter } from "next/navigation"; export function PostDetailSection({ postId }: { postId: string }) { const { isDesktop } = useWidth(); const { user } = useUser(); + const router = useRouter(); const { data: post } = usePostDetail(postId); const { likePost, unlikePost } = useLikePost(postId); const deletePost = useDeletePost(postId); @@ -46,62 +48,82 @@ export function PostDetailSection({ postId }: { postId: string }) { }, }); }, - onCancel: () => { - openModal("customForm", { isOpen: false, title: "", content: "", onConfirm: () => {}, onCancel: () => {} }); - }, + onCancel: () => + openModal("customForm", { + isOpen: false, + title: "", + content: "", + onConfirm: () => {}, + onCancel: () => {}, + }), }); }; - const dropdownOptions = [{ label: "삭제하기", onClick: handleDelete, disabled: deletePost.isPending }]; + const handleEdit = () => { + router.push(`/albatalk/edit/${postId}`); + }; + + const dropdownOptions = [ + { label: "수정하기", onClick: handleEdit }, + { label: "삭제하기", onClick: handleDelete, disabled: deletePost.isPending }, + ]; return ( -
+
{/* Header */}
-

{post?.title}

+

{post?.title}

{post?.writer.id === user?.id && }
+ {/* Divider Line */} +
+
+
+ {/* Author Info */}
- User Icon -
- - {post?.writer.nickname} - + User Icon +
+ {post?.writer.nickname} | - - {formatLocalDate(post?.createdAt || new Date())} - + {formatLocalDate(post?.createdAt || new Date())}
{/* Content */} -
+
{post?.content}
+ {/* Image */} +
+ {post?.imageUrl ? ( +
+ Post Image +
+ ) : null} +
+ {/* Footer */} -
+
Comment Icon - - {post?.commentCount} - + {post?.commentCount}
-
+
Like Icon - - {post?.likeCount} - + {post?.likeCount}
-
+
); } diff --git a/src/app/(pages)/albaTalk/edit/[id]/page.tsx b/src/app/(pages)/albaTalk/edit/[id]/page.tsx index c9ed2a7a..616ef36d 100644 --- a/src/app/(pages)/albaTalk/edit/[id]/page.tsx +++ b/src/app/(pages)/albaTalk/edit/[id]/page.tsx @@ -52,12 +52,15 @@ export default function EditTalk({ params }: { params: { id: string } }) { }); if (post.imageUrl) { - const initialImages = post.imageUrl.split(",").map((url, index) => ({ - file: null, - url, - id: `initial-${index}`, - })); - setImageList(initialImages); + setImageList([ + { + file: null, + url: post.imageUrl, + id: "initial-image", + }, + ]); + } else { + setImageList([]); } } }, [post, reset]); @@ -151,14 +154,16 @@ export default function EditTalk({ params }: { params: { id: string } }) {
@@ -228,19 +237,23 @@ export default function EditTalk({ params }: { params: { id: string } }) { {/* 모바일 버전 버튼 */}
- - +
); diff --git a/src/app/components/card/board/Comment.tsx b/src/app/components/card/board/Comment.tsx index bfc16d47..842c0367 100644 --- a/src/app/components/card/board/Comment.tsx +++ b/src/app/components/card/board/Comment.tsx @@ -9,6 +9,7 @@ import KebabDropdown from "@/app/components/button/dropdown/KebabDropdown"; import useModalStore from "@/store/modalStore"; import { useEditComment } from "@/hooks/queries/post/comment/useEditComment"; import { useDeleteComment } from "@/hooks/queries/post/comment/useDeleteComment"; +import { useQueryClient } from "@tanstack/react-query"; export interface CommentProps { id: string; @@ -22,6 +23,7 @@ const Comment = ({ id, nickname, updatedAt, content, isAuthor = false }: Comment const [isEditing, setIsEditing] = useState(false); const [editedContent, setEditedContent] = useState(content); const { openModal } = useModalStore(); + const queryClient = useQueryClient(); const editComment = useEditComment(id); const deleteComment = useDeleteComment(id); @@ -34,6 +36,7 @@ const Comment = ({ id, nickname, updatedAt, content, isAuthor = false }: Comment { onSuccess: () => { setIsEditing(false); + queryClient.invalidateQueries({ queryKey: ["comments"] }); }, } ); @@ -49,6 +52,7 @@ const Comment = ({ id, nickname, updatedAt, content, isAuthor = false }: Comment onConfirm: () => { deleteComment.mutate(undefined, { onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["comments"] }); openModal("customForm", { isOpen: false, title: "", @@ -84,13 +88,14 @@ const Comment = ({ id, nickname, updatedAt, content, isAuthor = false }: Comment value={editedContent} onChange={(e) => setEditedContent(e.target.value)} placeholder="댓글을 입력해주세요." - className="h-[132px] w-full lg:h-[160px]" + className="h-[90px] w-[344px] resize-none rounded-lg bg-background-200 p-4 md:w-[742px] lg:h-[100px] lg:w-[994px] xl:w-[1410px]" + size="h-[120px] w-[375px] md:w-[768px] lg:w-[1024px] xl:w-[1440px] lg:h-[144px]" variant="white" />
diff --git a/src/app/components/input/file/ImageInput/ImageInputPlaceHolder.tsx b/src/app/components/input/file/ImageInput/ImageInputPlaceHolder.tsx index b88c3856..4ec6202e 100644 --- a/src/app/components/input/file/ImageInput/ImageInputPlaceHolder.tsx +++ b/src/app/components/input/file/ImageInput/ImageInputPlaceHolder.tsx @@ -1,7 +1,7 @@ "use client"; import { HiUpload } from "react-icons/hi"; -import { useState, useRef } from "react"; +import { useState, useRef, useEffect } from "react"; import { toast } from "react-hot-toast"; import PreviewItem from "./PreviewItem"; @@ -15,16 +15,22 @@ interface ImageInputPlaceHolderProps { onImageUpload: (file: File) => Promise; onImagesChange: (images: ImageInputType[]) => void; size?: "small" | "large"; + initialImages?: ImageInputType[]; } const ImageInputPlaceHolder: React.FC = ({ onImageUpload, onImagesChange, size = "large", + initialImages = [], }) => { - const [imageList, setImageList] = useState([]); + const [imageList, setImageList] = useState(initialImages); const fileInputRef = useRef(null); + useEffect(() => { + setImageList(initialImages); + }, [initialImages]); + const handleFileChange = async (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0]; if (selectedFile) { diff --git a/src/app/components/input/textarea/BaseTextArea.tsx b/src/app/components/input/textarea/BaseTextArea.tsx index 0116112c..cf9c9e43 100644 --- a/src/app/components/input/textarea/BaseTextArea.tsx +++ b/src/app/components/input/textarea/BaseTextArea.tsx @@ -26,7 +26,7 @@ const BaseTextArea = forwardRef((props, hover: "hover:border-grayscale-300", }, }; - const defaultSize = "w-[327px] h-[132px] lg:w-[640px] lg:h-[160px]"; + const defaultSize = "w-[327px] h-[132px] lg:w-[640px] lg:h-[160px] md:w-[480px] lg:w-full"; const sizeStyles = props.size || defaultSize; // textareaStyle diff --git a/src/app/components/pagination/Pagination.tsx b/src/app/components/pagination/Pagination.tsx index 4b607c99..4abea41d 100644 --- a/src/app/components/pagination/Pagination.tsx +++ b/src/app/components/pagination/Pagination.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import PaginationBtn from "./paginationComponent/PaginationBtn"; import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io"; import useWidth from "@/hooks/useWidth"; @@ -12,88 +12,91 @@ interface PaginationProps { onPageChange: (page: number) => void; } -const Pagination = ({ totalPage, currentPage, onPageChange }: PaginationProps) => { +const Pagination = ({ totalPage, currentPage, onPageChange }: PaginationProps): JSX.Element => { const { isMobile } = useWidth(); const maxPageShow = isMobile ? 3 : 5; - const paginationNum = totalPage > maxPageShow ? maxPageShow : totalPage; // 페이지네이션 개수 유지 + const paginationNum = totalPage > maxPageShow ? maxPageShow : totalPage; const initialPageList = Array(paginationNum) .fill(null) .map((_, index) => index + 1); const [pageList, setPageList] = useState(initialPageList); - const firstPage = pageList?.[0]; + useEffect(() => { + const calculatePageList = () => { + const currentGroup = Math.ceil(currentPage / maxPageShow); + const start = (currentGroup - 1) * maxPageShow + 1; + const end = Math.min(start + maxPageShow - 1, totalPage); + + return Array(end - start + 1) + .fill(null) + .map((_, index) => start + index); + }; + + setPageList(calculatePageList()); + }, [currentPage, totalPage, maxPageShow]); + + const firstPage = pageList[0]; const lastPage = pageList[pageList.length - 1]; const prevDisabled = firstPage === 1; const nextDisabled = lastPage === totalPage || totalPage <= maxPageShow; const handleClickPrevBtn = () => { - if (firstPage === 1) { - return; - } else if (firstPage - maxPageShow <= 0) { - const newPageList = Array(paginationNum) - .fill(null) - .map((_, index) => index + 1); - setPageList(newPageList); - } else { - setPageList((prev) => prev.map((page) => page - maxPageShow)); - } + const newPage = Math.max(firstPage - maxPageShow, 1); + onPageChange(newPage); }; const handleClickNextBtn = () => { - if (totalPage <= maxPageShow || lastPage === totalPage) { - return; - } else if (lastPage === totalPage - 1 || lastPage + maxPageShow > totalPage) { - const newPageList = Array(paginationNum) - .fill(null) - .map((_, index) => totalPage - maxPageShow + 1 + index); - setPageList(newPageList); - } else { - setPageList((prev) => prev.map((page) => page + maxPageShow)); - } + const newPage = Math.min(lastPage + 1, totalPage); + onPageChange(newPage); }; const handleChangePage = (page: number) => { onPageChange(page); }; - // extraStyle로 전달 const activeStyle = "text-black-400 font-semibold"; const defaultStyle = "text-grayscale-200 font-medium lg:font-normal"; return ( -
-
  • +
  • +
      {pageList.map((page) => ( -
    • handleChangePage(page)}> - {page} +
    • +
    • ))}
    - {totalPage > maxPageShow + 2 && lastPage < totalPage - 1 ? ( + {totalPage > maxPageShow + 2 && lastPage < totalPage - 1 && ( <>
  • - ... + ...
  • -
  • handleChangePage(totalPage)}> - {totalPage} +
  • +
  • - ) : ( - <> )} -
  • +
  • -
    + + ); }; diff --git a/src/app/stories/design-system/components/pagination/Pagination.stories.tsx b/src/app/stories/design-system/components/pagination/Pagination.stories.tsx index 7e301426..81776a56 100644 --- a/src/app/stories/design-system/components/pagination/Pagination.stories.tsx +++ b/src/app/stories/design-system/components/pagination/Pagination.stories.tsx @@ -23,8 +23,21 @@ type Story = StoryObj; export const DefaultPagination: Story = { args: { totalPage: 8, + currentPage: 1, + onPageChange: (page: number) => console.log(`페이지 ${page}로 이동`), }, argTypes: { - totalPage: { control: "range" }, + totalPage: { + control: { type: "number", min: 1, max: 100 }, + description: "전체 페이지 수", + }, + currentPage: { + control: { type: "number", min: 1 }, + description: "현재 페이지", + }, + onPageChange: { + action: "clicked", + description: "페이지 변경 핸들러", + }, }, }; diff --git a/src/hooks/queries/post/comment/useDeleteComment.ts b/src/hooks/queries/post/comment/useDeleteComment.ts index 955799a5..c00d6f3e 100644 --- a/src/hooks/queries/post/comment/useDeleteComment.ts +++ b/src/hooks/queries/post/comment/useDeleteComment.ts @@ -28,7 +28,7 @@ export const useDeleteComment = (commentId: string) => { }, }); } else { - toast.error("댓글 삭제 중 오류가 발���했습니다.", { + toast.error("댓글 삭제 중 오류가 발생했습니다.", { style: { textAlign: "center", }, diff --git a/src/hooks/queries/user/me/useMyApplications.ts b/src/hooks/queries/user/me/useMyApplications.ts index e4ebce02..f6b939a1 100644 --- a/src/hooks/queries/user/me/useMyApplications.ts +++ b/src/hooks/queries/user/me/useMyApplications.ts @@ -1,30 +1,43 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import axios from "axios"; import { MyApplicationListResponse } from "@/types/response/user"; +import toast from "react-hot-toast"; interface UseMyApplicationsParams { - cursor?: string; limit?: number; status?: string; keyword?: string; } -export const useMyApplications = ({ cursor, limit, status, keyword }: UseMyApplicationsParams = {}) => { +export const useMyApplications = ({ limit = 10, status, keyword }: UseMyApplicationsParams = {}) => { const query = useInfiniteQuery({ queryKey: ["myApplications", { limit, status, keyword }], - queryFn: async () => { - const response = await axios.get("/api/users/me/applications", { - params: { - cursor, - limit, - status, - keyword, - }, - withCredentials: true, - }); - return response.data; + queryFn: async ({ pageParam }) => { + try { + const response = await axios.get("/api/users/me/applications", { + params: { + cursor: pageParam, + limit, + status, + keyword, + }, + withCredentials: true, + }); + return response.data; + } catch (error) { + if (axios.isAxiosError(error)) { + const errorMessage = error.response?.data?.message || "내 지원서 목록을 불러오는데 실패했습니다."; + toast.error(errorMessage); + } + throw error; + } + }, + getNextPageParam: (lastPage) => { + if (!lastPage.nextCursor || lastPage.data.length === 0) { + return undefined; + } + return lastPage.nextCursor; }, - getNextPageParam: (lastPage) => lastPage.nextCursor, initialPageParam: undefined, staleTime: 1000 * 60 * 5, gcTime: 1000 * 60 * 30, diff --git a/src/hooks/queries/user/me/useMyComments.ts b/src/hooks/queries/user/me/useMyComments.ts index 7cce63cd..b1594d2f 100644 --- a/src/hooks/queries/user/me/useMyComments.ts +++ b/src/hooks/queries/user/me/useMyComments.ts @@ -1,19 +1,46 @@ import { useQuery } from "@tanstack/react-query"; import axios from "axios"; import { MyCommentListResponse } from "@/types/response/user"; +import toast from "react-hot-toast"; -export const useMyComments = (params?: { page?: number; pageSize?: number }) => { - const query = useQuery({ - queryKey: ["myComments", params], +interface MyCommentsParams { + page?: number; + pageSize?: number; +} + +export const useMyComments = (params: MyCommentsParams = { page: 1, pageSize: 10 }) => { + const query = useQuery({ + queryKey: ["myComments", params.page, params.pageSize], queryFn: async () => { - const response = await axios.get("/api/users/me/comments", { - params, - withCredentials: true, - }); - return response.data; + try { + const response = await axios.get("/api/users/me/comments", { + params: { + page: params.page, + pageSize: params.pageSize, + }, + withCredentials: true, + }); + return response.data; + } catch (error) { + if (axios.isAxiosError(error)) { + const errorMessage = error.response?.data?.message || "내 댓글을 불러오는데 실패했습니다."; + toast.error(errorMessage, { + style: { + textAlign: "center", + }, + }); + } else { + toast.error("내 댓글을 불러오는 중 오류가 발생했습니다.", { + style: { + textAlign: "center", + }, + }); + } + throw error; + } }, - staleTime: 1000 * 60 * 5, - gcTime: 1000 * 60 * 30, + staleTime: 1000 * 60, // 1분 + gcTime: 1000 * 60 * 5, // 5분 }); return { diff --git a/src/hooks/queries/user/me/useMyForms.ts b/src/hooks/queries/user/me/useMyForms.ts index 1ee5cda5..1076c7a7 100644 --- a/src/hooks/queries/user/me/useMyForms.ts +++ b/src/hooks/queries/user/me/useMyForms.ts @@ -1,9 +1,9 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import axios from "axios"; import { MyFormListResponse } from "@/types/response/user"; +import toast from "react-hot-toast"; interface UseMyFormsParams { - cursor?: string; limit?: number; orderBy?: string; keyword?: string; @@ -11,24 +11,37 @@ interface UseMyFormsParams { isRecruiting?: boolean; } -export const useMyForms = ({ cursor, limit, orderBy, keyword, isPublic, isRecruiting }: UseMyFormsParams = {}) => { +export const useMyForms = ({ limit = 10, orderBy, keyword, isPublic, isRecruiting }: UseMyFormsParams = {}) => { const query = useInfiniteQuery({ queryKey: ["myForms", { limit, orderBy, keyword, isPublic, isRecruiting }], - queryFn: async () => { - const response = await axios.get("/api/users/me/forms", { - params: { - cursor, - limit, - orderBy, - keyword, - isPublic, - isRecruiting, - }, - withCredentials: true, - }); - return response.data; + queryFn: async ({ pageParam }) => { + try { + const response = await axios.get("/api/users/me/forms", { + params: { + cursor: pageParam, + limit, + orderBy, + keyword, + isPublic, + isRecruiting, + }, + withCredentials: true, + }); + return response.data; + } catch (error) { + if (axios.isAxiosError(error)) { + const errorMessage = error.response?.data?.message || "내 알바폼 목록을 불러오는데 실패했습니다."; + toast.error(errorMessage); + } + throw error; + } + }, + getNextPageParam: (lastPage) => { + if (!lastPage.nextCursor || lastPage.data.length === 0) { + return undefined; + } + return lastPage.nextCursor; }, - getNextPageParam: (lastPage) => lastPage.nextCursor, initialPageParam: undefined, staleTime: 1000 * 60 * 5, gcTime: 1000 * 60 * 30, diff --git a/src/hooks/queries/user/me/useMyPosts.ts b/src/hooks/queries/user/me/useMyPosts.ts index ed9de95f..51dca9bc 100644 --- a/src/hooks/queries/user/me/useMyPosts.ts +++ b/src/hooks/queries/user/me/useMyPosts.ts @@ -1,28 +1,41 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import axios from "axios"; import { MyPostListResponse } from "@/types/response/user"; +import toast from "react-hot-toast"; interface UseMyPostsParams { - cursor?: string; limit?: number; orderBy?: string; } -export const useMyPosts = ({ cursor, limit, orderBy }: UseMyPostsParams = {}) => { +export const useMyPosts = ({ limit = 10, orderBy }: UseMyPostsParams = {}) => { const query = useInfiniteQuery({ queryKey: ["myPosts", { limit, orderBy }], - queryFn: async () => { - const response = await axios.get("/api/users/me/posts", { - params: { - cursor, - limit, - orderBy, - }, - withCredentials: true, - }); - return response.data; + queryFn: async ({ pageParam }) => { + try { + const response = await axios.get("/api/users/me/posts", { + params: { + cursor: pageParam, + limit, + orderBy, + }, + withCredentials: true, + }); + return response.data; + } catch (error) { + if (axios.isAxiosError(error)) { + const errorMessage = error.response?.data?.message || "내 게시글 목록을 불러오는데 실패했습니다."; + toast.error(errorMessage); + } + throw error; + } + }, + getNextPageParam: (lastPage) => { + if (!lastPage.nextCursor || lastPage.data.length === 0) { + return undefined; + } + return lastPage.nextCursor; }, - getNextPageParam: (lastPage) => lastPage.nextCursor, initialPageParam: undefined, staleTime: 1000 * 60 * 5, gcTime: 1000 * 60 * 30, diff --git a/src/hooks/queries/user/me/useMyScraps.ts b/src/hooks/queries/user/me/useMyScraps.ts index e9a611c3..694cc8e5 100644 --- a/src/hooks/queries/user/me/useMyScraps.ts +++ b/src/hooks/queries/user/me/useMyScraps.ts @@ -1,42 +1,53 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import axios from "axios"; import { MyFormListResponse } from "@/types/response/user"; +import toast from "react-hot-toast"; interface UseMyScrapsParams { - cursor?: string; limit?: number; orderBy?: string; isPublic?: boolean; isRecruiting?: boolean; } -export const useMyScraps = ({ cursor, limit, orderBy, isPublic, isRecruiting }: UseMyScrapsParams = {}) => { +export const useMyScraps = ({ limit = 10, orderBy, isPublic, isRecruiting }: UseMyScrapsParams = {}) => { const query = useInfiniteQuery({ queryKey: ["myScraps", { limit, orderBy, isPublic, isRecruiting }], queryFn: async ({ pageParam }) => { - const response = await axios.get("/api/users/me/scrap", { - params: { - cursor: pageParam, - limit, - orderBy, - isPublic: isPublic === undefined ? null : isPublic, - isRecruiting: isRecruiting === undefined ? null : isRecruiting, - }, - withCredentials: true, - }); - return response.data; + try { + const response = await axios.get("/api/users/me/scrap", { + params: { + cursor: pageParam, + limit, + orderBy, + isPublic: isPublic === undefined ? null : isPublic, + isRecruiting: isRecruiting === undefined ? null : isRecruiting, + }, + withCredentials: true, + }); + return response.data; + } catch (error) { + if (axios.isAxiosError(error)) { + const errorMessage = error.response?.data?.message || "스크랩한 알바폼 목록을 불러오는데 실패했습니다."; + toast.error(errorMessage); + } + throw error; + } }, - initialPageParam: cursor, - getNextPageParam: (lastPage) => lastPage.nextCursor ?? null, + getNextPageParam: (lastPage) => { + if (!lastPage.nextCursor || lastPage.data.length === 0) { + return undefined; + } + return lastPage.nextCursor; + }, + initialPageParam: undefined, staleTime: 1000 * 60 * 5, gcTime: 1000 * 60 * 30, }); return { ...query, - data: query.data, - isLoading: query.isLoading, - isFetching: query.isFetching, + isPending: query.isPending, error: query.error, }; };