diff --git a/src/app/[groupId]/_components/team-banner-members.tsx b/src/app/[groupId]/_components/team-banner-members.tsx new file mode 100644 index 00000000..f122b03e --- /dev/null +++ b/src/app/[groupId]/_components/team-banner-members.tsx @@ -0,0 +1,9 @@ +import { Member } from "@/types/members"; + +interface TeamBannerMembersProps { + members: Member[]; +} + +const TeamBannerMembers = () => {}; + +export default TeamBannerMembers; diff --git a/src/app/[groupId]/_components/team-body/team-body.tsx b/src/app/[groupId]/_components/team-body/team-body.tsx index 7e0bd2ee..3177e77c 100644 --- a/src/app/[groupId]/_components/team-body/team-body.tsx +++ b/src/app/[groupId]/_components/team-body/team-body.tsx @@ -9,6 +9,7 @@ import useDeleteTaskList from "@/hooks/api/task/use-delete-task-list"; import usePatchTaskList from "@/hooks/api/task/use-patch-task-list"; import usePostTaskList from "@/hooks/api/task/use-post-task-list"; import usePrompt from "@/hooks/use-prompt"; +import useToast from "@/hooks/use-toast"; import { useRouter } from "next/navigation"; import { useState } from "react"; import TaskListsSection from "./task-lists-section"; @@ -38,9 +39,11 @@ const TeamBody = ({ taskLists, groupId }: TeamBodyProps) => { (a, b) => a.displayIndex - b.displayIndex ); - const { mutate: postTaskList } = usePostTaskList(); - const { mutate: patchTaskList } = usePatchTaskList(groupId); + const { mutate: postTaskList, isPending: isPostPending } = usePostTaskList(); + const { mutate: patchTaskList, isPending: isPatchPending } = + usePatchTaskList(groupId); const { mutate: deleteTaskList } = useDeleteTaskList(groupId); + const { success, error, warning } = useToast(); const [selectedTaskList, setSelectedTaskList] = useState( null @@ -65,17 +68,36 @@ const TeamBody = ({ taskLists, groupId }: TeamBodyProps) => { } = usePrompt(); const handleAddTaskList = (name: string) => { - closeAddModal(); - postTaskList({ groupId, name }, {}); + postTaskList( + { groupId, name }, + { + onSuccess: () => { + success("할 일을 추가했습니다. "); + closeAddModal(); + }, + onError: (err: any) => { + const msg = err.response.data.message ?? "할 일 추가에 실패했습니다."; + error(msg); + }, + } + ); }; const handlePatchTaskList = (newName: string) => { if (!selectedTaskList) return; - closeChangeModal(); patchTaskList( { groupId, taskListId: selectedTaskList.id, name: newName }, - {} + { + onSuccess: () => { + closeChangeModal(); + }, + onError: (err: any) => { + const msg = + err.response.data.message ?? "할 일 목록 수정에 실패했습니다."; + error(msg); + }, + } ); }; @@ -100,8 +122,12 @@ const TeamBody = ({ taskLists, groupId }: TeamBodyProps) => { { groupId, taskListId: selectedTaskList.id }, { onSuccess: () => { + success("할 일을 삭제했습니다."); closeDeleteModal(); }, + onError: () => { + error("할 일 삭제에 실패했습니다"); + }, } ); }; @@ -140,7 +166,10 @@ const TeamBody = ({ taskLists, groupId }: TeamBodyProps) => { - + @@ -148,6 +177,7 @@ const TeamBody = ({ taskLists, groupId }: TeamBodyProps) => { )} diff --git a/src/app/[groupId]/_components/team-page-client.tsx b/src/app/[groupId]/_components/team-page-client.tsx index d2293baf..3fbd40e1 100644 --- a/src/app/[groupId]/_components/team-page-client.tsx +++ b/src/app/[groupId]/_components/team-page-client.tsx @@ -1,6 +1,6 @@ "use client"; -import { TeamBannerAdmin, TeamBannerMember } from "@/components/index"; +import { TeamBannerAdmin } from "@/components/index"; import BannerAdminSkeleton from "@/components/skeleton/team-skeleton/banner-admin-skeleton"; import TeamBodySkeleton from "@/components/skeleton/team-skeleton/team-body-skeleton"; import TeamMemberSectionSkeleton from "@/components/skeleton/team-skeleton/team-member-section-skeleton"; @@ -20,7 +20,7 @@ const TeamPageClient = ({ groupId }: TeamPageClientProps) => { const { data: groupInfo, isPending } = useGetGroupInfo(groupId); const isAdmin = isUserAdmin(userInfo, groupId); - if (isPending || !groupInfo) { + if (isPending || !groupInfo || !userInfo) { return (
{ )} >
- {isAdmin ? ( - - ) : ( - - )} +
diff --git a/src/app/[groupId]/editteam/_components/edit-team.tsx b/src/app/[groupId]/editteam/_components/edit-team.tsx index 62291b5c..5f22a701 100644 --- a/src/app/[groupId]/editteam/_components/edit-team.tsx +++ b/src/app/[groupId]/editteam/_components/edit-team.tsx @@ -3,13 +3,15 @@ import { Button, Icon, + LoadingSpinner, ProfileEdit, TextInput, - LoadingSpinner, } from "@/components"; +import EditTeamSkeleton from "@/components/skeleton/editteam-skeleton/editteam-skeleton"; import useGetGroupInfo from "@/hooks/api/group/use-get-group-info"; import usePatchGroup from "@/hooks/api/group/use-patch-group"; import { useImageUpload } from "@/hooks/image-upload/use-image-upload"; +import useToast from "@/hooks/use-toast"; import cn from "@/utils/clsx"; import { useEffect, useRef, useState } from "react"; @@ -19,8 +21,9 @@ interface EditTeamProps { const EditTeam = ({ groupId }: EditTeamProps) => { const fileInputRef = useRef(null); - const { data: groupInfo } = useGetGroupInfo(groupId); - + const { data: groupInfo, isPending: isGroupDataPending } = + useGetGroupInfo(groupId); + const { success, error, warning } = useToast(); const [groupName, setGroupName] = useState(""); const [initialImage, setInitialImage] = useState(); @@ -60,12 +63,22 @@ const EditTeam = ({ groupId }: EditTeamProps) => { }; const onSubmit = () => { - patchGroup({ - name: groupName, - image: previews[0]?.url ?? initialImage ?? null, - }); + patchGroup( + { + name: groupName, + image: previews[0]?.url ?? initialImage ?? null, + }, + { + onSuccess: () => { + success("팀 정보를 수정했습니다."); + }, + onError: () => { + error("팀 정보 수정을 실패했습니다."); + }, + } + ); }; - if (!groupInfo) return null; + const handleRemoveImage = () => { if (previews[0]) { removeImage(previews[0].id); @@ -73,6 +86,9 @@ const EditTeam = ({ groupId }: EditTeamProps) => { setInitialImage(null); }; + if (isGroupDataPending || !groupInfo) { + return ; + } return (
{ const { data: comments, isPending } = useGetComments(Number(taskId)); - const { mutate: patchTaskComment } = usePatchTaskComment(); + const { mutate: patchComment } = usePatchTaskComment(); + const { mutate: deleteComment } = useDeleteTaskComment(); if (isPending || !comments) return ; return (
    - {comments.map((comment, idx) => { - return ( -
  • - {idx !== 0 && idx < comments.length &&
    } - -
  • - ); - })} + {comments.map((comment, idx) => ( +
  • + {idx !== 0 && idx < comments.length &&
    } + + patchComment({ taskId, commentId, content }) + } + onDelete={(commentId) => deleteComment({ taskId, commentId })} + /> +
  • + ))}
); diff --git a/src/app/[groupId]/tasklist/_components/task-list-container.tsx b/src/app/[groupId]/tasklist/_components/task-list-container.tsx index c9f4b00a..b165f204 100644 --- a/src/app/[groupId]/tasklist/_components/task-list-container.tsx +++ b/src/app/[groupId]/tasklist/_components/task-list-container.tsx @@ -11,8 +11,10 @@ import { import usePostTaskList from "@/hooks/api/task/use-post-task-list"; import usePrompt from "@/hooks/use-prompt"; import { countDoneTask } from "@/utils/util"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; import Skeleton from "react-loading-skeleton"; +import Link from "next/link"; interface TodoContainerProps { groupId: number; @@ -27,6 +29,7 @@ const TaskListContainer = ({ taskList, isPending, }: TodoContainerProps) => { + const pathName = usePathname(); const { mutate: createTaskList, isPending: isPostPending } = usePostTaskList(); const { Modal: AddTaskListModal, openPrompt, closePrompt } = usePrompt(true); @@ -36,6 +39,12 @@ const TaskListContainer = ({ createTaskList({ groupId: groupId, name: name }); }; + const [optimisticTaskListId, setOptimisticTaskListId] = useState(taskListId); + + useEffect(() => { + setOptimisticTaskListId(taskListId); + }, [taskListId]); + return (
{ return ( -
  • +
  • setOptimisticTaskListId(task.id)} + > + + )} diff --git a/src/app/boards/[articleId]/_components/article-comments/article-comments.tsx b/src/app/boards/[articleId]/_components/article-comments/article-comments.tsx index 46f8272d..fc0bf951 100644 --- a/src/app/boards/[articleId]/_components/article-comments/article-comments.tsx +++ b/src/app/boards/[articleId]/_components/article-comments/article-comments.tsx @@ -9,6 +9,8 @@ import { usePostArticleComment } from "@/hooks/api/articles/use-post-article-com import { useGetUserInfoQuery } from "@/hooks/api/user/use-get-user-info-query"; import useGetArticleComments from "@/hooks/api/articles/use-get-article-comments"; import useToggleArticleLike from "@/hooks/api/articles/use-toggle-article-like"; +import usePatchArticleComment from "@/hooks/api/articles/use-patch-article-comment"; +import useDeleteArticleComment from "@/hooks/api/articles/use-delete-article-comment"; import LikeButton from "@/components/lottie/LikeButton"; import DefaultProfile from "@/assets/icons/ic-user.svg"; @@ -18,6 +20,8 @@ interface ArticleCommentsProps { const ArticleComments = ({ article }: ArticleCommentsProps) => { const { mutate, isPending } = usePostArticleComment(); + const { mutate: patchComment } = usePatchArticleComment(); + const { mutate: deleteComment } = useDeleteArticleComment(); const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useGetArticleComments({ articleId: article.id }); @@ -129,7 +133,15 @@ const ArticleComments = ({ article }: ArticleCommentsProps) => { {allComments.map((comment) => (

    - + + patchComment({ commentId, content }) + } + onDelete={(commentId) => + deleteComment({ commentId, articleId: article.id }) + } + />
    ))}
    diff --git a/src/app/boards/[articleId]/_components/article-contents/article-contents.tsx b/src/app/boards/[articleId]/_components/article-contents/article-contents.tsx index e5689b3f..fa90b518 100644 --- a/src/app/boards/[articleId]/_components/article-contents/article-contents.tsx +++ b/src/app/boards/[articleId]/_components/article-contents/article-contents.tsx @@ -1,8 +1,6 @@ "use client"; -import Link from "next/link"; import Image from "next/image"; -import { Button } from "@/components/index"; import { Article } from "@/types/article"; interface ArticleContentsProps { @@ -19,7 +17,6 @@ const ArticleContents = ({ article }: ArticleContentsProps) => { width={140} height={140} className="rounded-lg tablet:h-[200px] tablet:w-[200px]" - onError={() => console.log("이미지 로드 실패:", article.image)} /> )}

    {article.content}

    diff --git a/src/app/boards/[articleId]/_components/article-detail-client.tsx b/src/app/boards/[articleId]/_components/article-detail-client.tsx index 0ae7118a..0dad50a9 100644 --- a/src/app/boards/[articleId]/_components/article-detail-client.tsx +++ b/src/app/boards/[articleId]/_components/article-detail-client.tsx @@ -25,8 +25,8 @@ export default function ArticleDetailClient({ const articleData = data.article; return ( -
    -
    +
    +
    diff --git a/src/app/boards/[articleId]/edit/_components/article-edit-contents/article-edit-contents.tsx b/src/app/boards/[articleId]/edit/_components/article-edit-contents/article-edit-contents.tsx index 577853b0..f9fc0d23 100644 --- a/src/app/boards/[articleId]/edit/_components/article-edit-contents/article-edit-contents.tsx +++ b/src/app/boards/[articleId]/edit/_components/article-edit-contents/article-edit-contents.tsx @@ -65,53 +65,49 @@ const ArticleEditContents = ({ article }: ArticleEditContentsProps) => {
    -
    -

    게시글 수정

    +

    게시글 수정

    -
    -
    - 제목 - * -
    - - {errors.title && ( - {errors.title.message} - )} +
    +
    + 제목 + *
    + + {errors.title && ( + {errors.title.message} + )} +
    -
    -
    - 내용 - * -
    - - {errors.content && ( - - {errors.content.message} - - )} +
    +
    + 내용 + *
    + + {errors.content && ( + {errors.content.message} + )} +
    -
    - 이미지 - -
    +
    + 이미지 +
    -
    ); diff --git a/src/components/modal-ui/change-task-list-modal-ui.tsx b/src/components/modal-ui/change-task-list-modal-ui.tsx index cdc316dc..0d63134f 100644 --- a/src/components/modal-ui/change-task-list-modal-ui.tsx +++ b/src/components/modal-ui/change-task-list-modal-ui.tsx @@ -1,14 +1,16 @@ "use client"; -import { Button } from "@/components"; +import { Button, LoadingSpinner } from "@/components"; import { ChangeEvent, useState } from "react"; const ChangeTaskListModalUI = ({ taskTitle, handleClick, + isPending, }: { taskTitle: string; handleClick: (name: string) => void; + isPending?: boolean; }) => { const [input, setInput] = useState(taskTitle); @@ -27,8 +29,11 @@ const ChangeTaskListModalUI = ({ } />
    -
    ); diff --git a/src/components/modal-ui/delete-modal-ui.tsx b/src/components/modal-ui/delete-modal-ui.tsx index e6d5f262..0a763e50 100644 --- a/src/components/modal-ui/delete-modal-ui.tsx +++ b/src/components/modal-ui/delete-modal-ui.tsx @@ -1,6 +1,7 @@ import { ReactNode } from "react"; import Button from "../button/button"; import Icon from "../icon/Icon"; +import LoadingSpinner from "../loading-spinner/loading-spinner"; interface DeleteModalUIProps { contents: ReactNode; @@ -8,6 +9,7 @@ interface DeleteModalUIProps { handleClick: () => void; handleClose: () => void; confirmMessage?: string; + isPeding?: boolean; } const DeleteModalUI = ({ @@ -16,6 +18,7 @@ const DeleteModalUI = ({ handleClick, handleClose, confirmMessage, + isPeding, }: DeleteModalUIProps) => { return (
    @@ -40,8 +43,9 @@ const DeleteModalUI = ({ variant="alert" onClick={handleClick} className="h-[48px] tablet:w-[138px]" + disabled={isPeding} > - {confirmMessage ?? "삭제하기"} + {isPeding ? : (confirmMessage ?? "삭제하기")}
  • diff --git a/src/components/reply/reply.stories.tsx b/src/components/reply/reply.stories.tsx index b49d8b61..2c9540a8 100644 --- a/src/components/reply/reply.stories.tsx +++ b/src/components/reply/reply.stories.tsx @@ -12,6 +12,12 @@ const meta = { comment: { description: "댓글 데이터 객체", }, + onEdit: { + description: "댓글 수정 콜백", + }, + onDelete: { + description: "댓글 삭제 콜백", + }, }, decorators: [ (Story) => ( @@ -39,6 +45,9 @@ export const Default: Story = { createdAt: "2024-07-25T06:30:00.000Z", updatedAt: "2024-07-25T06:30:00.000Z", }, + onEdit: (commentId: number, content: string) => + console.log("댓글 수정:", commentId, content), + onDelete: (commentId: number) => console.log("댓글 삭제:", commentId), }, }; @@ -57,6 +66,9 @@ export const LongContent: Story = { createdAt: "2024-07-25T06:30:00.000Z", updatedAt: "2024-07-25T06:30:00.000Z", }, + onEdit: (commentId: number, content: string) => + console.log("댓글 수정:", commentId, content), + onDelete: (commentId: number) => console.log("댓글 삭제:", commentId), }, }; @@ -74,6 +86,9 @@ export const ShortContent: Story = { createdAt: "2024-07-25T06:30:00.000Z", updatedAt: "2024-07-25T06:30:00.000Z", }, + onEdit: (commentId: number, content: string) => + console.log("댓글 수정:", commentId, content), + onDelete: (commentId: number) => console.log("댓글 삭제:", commentId), }, }; @@ -91,6 +106,9 @@ export const LongNickname: Story = { createdAt: "2024-07-25T06:30:00.000Z", updatedAt: "2024-07-25T06:30:00.000Z", }, + onEdit: (commentId: number, content: string) => + console.log("댓글 수정:", commentId, content), + onDelete: (commentId: number) => console.log("댓글 삭제:", commentId), }, }; @@ -107,5 +125,8 @@ export const NoProfileImage: Story = { createdAt: "2024-07-25T06:30:00.000Z", updatedAt: "2024-07-25T06:30:00.000Z", }, + onEdit: (commentId: number, content: string) => + console.log("댓글 수정:", commentId, content), + onDelete: (commentId: number) => console.log("댓글 삭제:", commentId), }, }; diff --git a/src/components/reply/reply.tsx b/src/components/reply/reply.tsx index f7779f1f..d83f636f 100644 --- a/src/components/reply/reply.tsx +++ b/src/components/reply/reply.tsx @@ -1,3 +1,6 @@ +"use client"; + +import { useState } from "react"; import cn from "@/utils/clsx"; import Button from "../button/button"; import Icon from "../icon/Icon"; @@ -6,18 +9,36 @@ import { Comment } from "@/types/index"; import TextareaAutosize from "react-textarea-autosize"; import DefaultProfile from "@/assets/icons/ic-user.svg"; import { toDotDateString } from "@/utils/date-util"; -import { useCommentHandlers } from "@/hooks/comment-handlers/use-comment-handlers"; import { useGetUserInfoQuery } from "@/hooks/api/user/use-get-user-info-query"; interface CommentProps { comment: Comment; - articleId?: number; + onEdit: (commentId: number, content: string) => void; + onDelete: (commentId: number) => void; } -const Reply = ({ comment, articleId }: CommentProps) => { +const Reply = ({ comment, onEdit, onDelete }: CommentProps) => { const { data: userInfo } = useGetUserInfoQuery(); - const { isEditing, editedContent, setEditedContent, ...handlers } = - useCommentHandlers(comment, articleId); + const [isEditing, setIsEditing] = useState(false); + const [editedContent, setEditedContent] = useState(comment.content); + + const handleEditClick = () => { + setIsEditing(true); + }; + + const handleEdit = () => { + onEdit(comment.id, editedContent); + setIsEditing(false); + }; + + const handleDelete = () => { + onDelete(comment.id); + }; + + const handleCancel = () => { + setEditedContent(comment.content); + setIsEditing(false); + }; const isWriter = userInfo?.id === comment.writer.id; @@ -63,8 +84,8 @@ const Reply = ({ comment, articleId }: CommentProps) => { } items={[ - { label: "수정하기", onClick: handlers.handleEdit }, - { label: "삭제하기", onClick: handlers.handleDelete }, + { label: "수정하기", onClick: handleEditClick }, + { label: "삭제하기", onClick: handleDelete }, ]} isWidthFull={false} /> @@ -82,7 +103,7 @@ const Reply = ({ comment, articleId }: CommentProps) => { />
    diff --git a/src/components/skeleton/list-skeleton/task-item-skeleton.tsx b/src/components/skeleton/list-skeleton/task-item-skeleton.tsx index 275a8fed..407b28c2 100644 --- a/src/components/skeleton/list-skeleton/task-item-skeleton.tsx +++ b/src/components/skeleton/list-skeleton/task-item-skeleton.tsx @@ -8,6 +8,7 @@ const TaskItemSkeleton = () => { key={idx} containerClassName="h-[68px] tablet:h-[76px] w-full" className="h-full" + borderRadius={8} /> ))}
    diff --git a/src/components/skeleton/team-skeleton/banner-admin-skeleton.tsx b/src/components/skeleton/team-skeleton/banner-admin-skeleton.tsx index 91cbc716..f43ce9fc 100644 --- a/src/components/skeleton/team-skeleton/banner-admin-skeleton.tsx +++ b/src/components/skeleton/team-skeleton/banner-admin-skeleton.tsx @@ -11,7 +11,7 @@ const BannerAdminSkeleton = () => { )} >
    - +
    { "tablet:h-[108px]" )} > - +
    ); diff --git a/src/components/skeleton/team-skeleton/banner-member-skeleton.tsx b/src/components/skeleton/team-skeleton/banner-member-skeleton.tsx index c6410b97..f07f85b7 100644 --- a/src/components/skeleton/team-skeleton/banner-member-skeleton.tsx +++ b/src/components/skeleton/team-skeleton/banner-member-skeleton.tsx @@ -16,7 +16,7 @@ const BannerMemberSkeleton = ({ className }: BannerMemberSkeletonProps) => { )} >
    - +
    ); diff --git a/src/components/skeleton/team-skeleton/team-body-skeleton.tsx b/src/components/skeleton/team-skeleton/team-body-skeleton.tsx index a64db5ff..b71ccddb 100644 --- a/src/components/skeleton/team-skeleton/team-body-skeleton.tsx +++ b/src/components/skeleton/team-skeleton/team-body-skeleton.tsx @@ -7,14 +7,15 @@ const TeamBodySkeleton = () => {
    - +
    {Array.from({ length: 3 }).map((_, idx) => ( -
    + className="h-[40px] w-full" + borderRadius="12px" + /> ))}
    diff --git a/src/components/skeleton/team-skeleton/team-member-section-skeleton.tsx b/src/components/skeleton/team-skeleton/team-member-section-skeleton.tsx index 8acc857e..3ee0dc50 100644 --- a/src/components/skeleton/team-skeleton/team-member-section-skeleton.tsx +++ b/src/components/skeleton/team-skeleton/team-member-section-skeleton.tsx @@ -6,15 +6,11 @@ const TeamMemberSectionSkeleton = () => {
    {Array.from({ length: 3 }).map((_, idx) => ( -
    -
    - - -
    -
    + className="h-[72px] w-full" + borderRadius="12px" + /> ))}
    diff --git a/src/components/task-card/task-card.tsx b/src/components/task-card/task-card.tsx index 65c63aa9..d433994b 100644 --- a/src/components/task-card/task-card.tsx +++ b/src/components/task-card/task-card.tsx @@ -5,8 +5,6 @@ import useDeleteTaskList from "@/hooks/api/task/use-delete-task-list"; import usePatchTaskList from "@/hooks/api/task/use-patch-task-list"; import usePrompt from "@/hooks/use-prompt"; import cn from "@/utils/clsx"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; import { MouseEventHandler } from "react"; import Badge, { BadgeProps } from "../badge/badge"; import Button from "../button/button"; @@ -34,7 +32,6 @@ const TaskCard = ({ completed, handleClickCheckbox, }: TaskCardProps) => { - const pathName = usePathname(); const { Modal: DeleteModal, openPrompt: openDeleteModal, @@ -63,22 +60,19 @@ const TaskCard = ({
    - -

    - {taskTitle} -

    - -
    +

    + {taskTitle} +

    +
    e.stopPropagation()}>
    diff --git a/src/components/team-banner/team-banner-admin/team-banner-admin-header.tsx b/src/components/team-banner/team-banner-admin/team-banner-admin-header.tsx index b3a37da7..6e608107 100644 --- a/src/components/team-banner/team-banner-admin/team-banner-admin-header.tsx +++ b/src/components/team-banner/team-banner-admin/team-banner-admin-header.tsx @@ -53,7 +53,7 @@ const TeamBannerAdminHeader = ({ ) ) : ( )} diff --git a/src/components/team-banner/team-banner-admin/team-banner-admin.tsx b/src/components/team-banner/team-banner-admin/team-banner-admin.tsx index 8b4a428e..37280347 100644 --- a/src/components/team-banner/team-banner-admin/team-banner-admin.tsx +++ b/src/components/team-banner/team-banner-admin/team-banner-admin.tsx @@ -4,9 +4,11 @@ import Dropdown from "@/components/dropdown-components/dropdown"; import Icon from "@/components/icon/Icon"; import DeleteModalUI from "@/components/modal-ui/delete-modal-ui"; import useDeleteGroup from "@/hooks/api/group/use-delete-group"; +import useDeleteGroupMember from "@/hooks/api/group/use-delete-group-member"; import { useGetUserInfoQuery } from "@/hooks/api/user/use-get-user-info-query"; import useMediaQuery from "@/hooks/use-media-query"; import usePrompt from "@/hooks/use-prompt"; +import useToast from "@/hooks/use-toast"; import { Member } from "@/types/members"; import cn from "@/utils/clsx"; import { useRouter } from "next/navigation"; @@ -36,6 +38,8 @@ interface TeamBannerAdminProps { showProfileListOnPc?: boolean; className?: string; groupId: number; + isAdmin: boolean; + myId: number; } const TeamBannerAdmin = ({ @@ -46,38 +50,87 @@ const TeamBannerAdmin = ({ showProfileListOnPc = true, className, groupId, + isAdmin, + myId, }: TeamBannerAdminProps) => { const isPc = useMediaQuery("(min-width: 1280px)"); const router = useRouter(); - const { Modal, openPrompt, closePrompt } = usePrompt(false); - const { mutate: deleteGroup } = useDeleteGroup(groupId); + + const { + Modal: DeleteTeamModal, + openPrompt: openDeleteTeamModal, + closePrompt: closeDeleteTeamModal, + } = usePrompt(false); + + const { + Modal: LeaveTeamModal, + openPrompt: openLeaveTeamModal, + closePrompt: closeLeaveTeamModal, + } = usePrompt(); + + const { mutate: deleteGroup, isPending: isDeletePeding } = + useDeleteGroup(groupId); + + const { mutate: deleteGroupMember, isPending: isDeleteGroupMemberPending } = + useDeleteGroupMember(groupId); + const { refetch: refetchUserInfo } = useGetUserInfoQuery(); + const { success, error, warning } = useToast(); const handleEditDropdown = () => { router.push(`/${groupId}/editteam`); }; const handleDeleteDropdown = () => { - openPrompt(); + openDeleteTeamModal(); }; const handleConfirmDelete = () => { deleteGroup(groupId, { onSuccess: async () => { + success("팀 삭제에 성공했습니다."); const { data: newUserInfo } = await refetchUserInfo(); const newGroups = newUserInfo?.memberships ?? []; if (newGroups.length === 0) { - await router.push("/board"); - closePrompt(); + await router.push("/boards"); + closeDeleteTeamModal(); return; } await router.push(`/${newGroups[0].groupId}`); - closePrompt(); + closeDeleteTeamModal(); + }, + onError: () => { + error("팀 삭제에 실패했습니다."); }, }); }; + const handleLeaveTeam = () => { + deleteGroupMember(myId, { + onSuccess: async () => { + success("팀을 나갔습니다."); + const { data: newUserInfo } = await refetchUserInfo(); + const newGroups = newUserInfo?.memberships ?? []; + if (newGroups.length === 0) { + await router.push("/boards"); + closeLeaveTeamModal(); + return; + } + + await router.push(`/${newGroups[0].groupId}`); + closeLeaveTeamModal(); + }, + }); + }; + + const adminItems = [ + { label: "수정하기", onClick: handleEditDropdown }, + { label: "삭제하기", onClick: handleDeleteDropdown }, + ]; + + const memberItems = [{ label: "팀 나가기", onClick: openLeaveTeamModal }]; + return (
    {!isPc && (
    - - } - items={[ - { label: "수정하기", onClick: handleEditDropdown }, - { label: "삭제하기", onClick: handleDeleteDropdown }, - ]} - menuAlign="end" - /> + { + + } + items={isAdmin ? adminItems : memberItems} + menuAlign="end" + /> + }
    )} @@ -130,26 +182,16 @@ const TeamBannerAdmin = ({ className="h-[24px] w-[24px] cursor-pointer" /> } - items={[ - { - label: "수정하기", - onClick: handleEditDropdown, - }, - { - label: "삭제하기", - onClick: handleDeleteDropdown, - }, - //TODO: 클릭시 로직 추가 - ]} + items={isAdmin ? adminItems : memberItems} menuAlign="end" /> )}
    - + '{groupName}' @@ -158,8 +200,26 @@ const TeamBannerAdmin = ({ } description="삭제 후에는 되돌릴 수 없습니다." + isPeding={isDeletePeding} + /> + + + + '{groupName}' +
    +
    + 팀에서 나가시겠어요? + + } + description="언제든 다시 참여하실 수 있어요" + handleClick={handleLeaveTeam} + handleClose={closeLeaveTeamModal} + confirmMessage="나가기" + isPeding={isDeleteGroupMemberPending} /> -
    +
    ); }; diff --git a/src/hooks/comment-handlers/use-comment-handlers.ts b/src/hooks/comment-handlers/use-comment-handlers.ts deleted file mode 100644 index 1deed7ae..00000000 --- a/src/hooks/comment-handlers/use-comment-handlers.ts +++ /dev/null @@ -1,43 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { Comment } from "@/types/index"; -import useDeleteArticleComment from "@/hooks/api/articles/use-delete-article-comment"; -import usePatchArticleComment from "@/hooks/api/articles/use-patch-article-comment"; - -export const useCommentHandlers = (comment: Comment, articleId?: number) => { - const [isEditing, setIsEditing] = useState(false); - const [editedContent, setEditedContent] = useState(comment.content); - - const { mutate: deleteComment } = useDeleteArticleComment(); - const { mutate: patchComment } = usePatchArticleComment(); - - const handleEdit = () => { - setIsEditing(true); - }; - - const handleSave = () => { - patchComment({ commentId: comment.id, content: editedContent }); - setIsEditing(false); - }; - - const handleDelete = () => { - if (!articleId) return; - deleteComment({ commentId: comment.id, articleId }); - }; - - const handleCancel = () => { - setEditedContent(comment.content); - setIsEditing(false); - }; - - return { - isEditing, - editedContent, - setEditedContent, - handleEdit, - handleSave, - handleDelete, - handleCancel, - }; -}; diff --git a/src/hooks/use-prompt.tsx b/src/hooks/use-prompt.tsx index 7ce4a88c..81e83cda 100644 --- a/src/hooks/use-prompt.tsx +++ b/src/hooks/use-prompt.tsx @@ -61,6 +61,7 @@ const usePrompt = (showCloseBtn = false) => { const handleBackdropClick = (e: MouseEvent) => { if (e.target === e.currentTarget) { closePrompt(); + e.stopPropagation(); } };