diff --git a/apps/client/src/app/@modal/(...)group/[groupId]/solved-detail/components/CommentSection/index.tsx b/apps/client/src/app/@modal/(...)group/[groupId]/solved-detail/components/CommentSection/index.tsx index 3574fc69..aa2b565a 100644 --- a/apps/client/src/app/@modal/(...)group/[groupId]/solved-detail/components/CommentSection/index.tsx +++ b/apps/client/src/app/@modal/(...)group/[groupId]/solved-detail/components/CommentSection/index.tsx @@ -3,15 +3,21 @@ import { useCommentMutation, useDeleteCommentMutation, + useEditCommentMutation, } from "@/app/api/comments/mutation"; import { useCommentListQueryObject } from "@/app/api/comments/query"; import CommentBox from "@/shared/component/CommentBox"; import CommentInput from "@/shared/component/CommentInput"; import { useQuery } from "@tanstack/react-query"; import { useSession } from "next-auth/react"; -import { type FormEvent, useEffect, useRef, useState } from "react"; +import { + type FormEvent, + useCallback, + useEffect, + useRef, + useState, +} from "react"; import { commentInputStyle, sectionWrapper, ulStyle } from "./index.css"; -import { CommentsProvider } from "./provider"; type CommentSectionProps = { solutionId: number; @@ -21,10 +27,15 @@ const CommentSection = ({ solutionId }: CommentSectionProps) => { const commentRef = useRef(null); const [comment, setComment] = useState(""); const { data: session } = useSession(); - + const nickname = session?.user?.nickname; const { data: comments } = useQuery(useCommentListQueryObject(solutionId)); const { mutate: commentAction } = useCommentMutation(solutionId); const { mutate: deleteMutate } = useDeleteCommentMutation(solutionId); + const { mutate: commentEditMutate } = useEditCommentMutation(); + + const onCommentEdit = useCallback((commentId: number, content: string) => { + commentEditMutate({ commentId, content }); + }, []); const handleCommentSubmit = (e: FormEvent) => { e.preventDefault(); @@ -44,19 +55,17 @@ const CommentSection = ({ solutionId }: CommentSectionProps) => { return (
    - - {comments - ?.sort((a, b) => +new Date(a.createdAt) - +new Date(b.createdAt)) - .map((item) => ( - - ))} - + {comments + ?.sort((a, b) => +new Date(a.createdAt) - +new Date(b.createdAt)) + .map((commentContent) => ( + + ))}
void; - handleReset: () => void; - solutionId: number; -}; - -export const CommentsContext = createContext({} as Context); - -export const CommentsProvider = ({ - children, - solutionId, -}: { children: ReactNode; solutionId: number }) => { - const [editingItem, setEditingItem] = useState(null); - - const handleEditItem = (id: number) => { - setEditingItem((prev) => (prev === id ? null : id)); - }; - - const handleReset = () => { - setEditingItem(null); - }; - - return ( - - {children} - - ); -}; diff --git a/apps/client/src/app/api/comments/mutation.ts b/apps/client/src/app/api/comments/mutation.ts index 6f32ed9b..d709d686 100644 --- a/apps/client/src/app/api/comments/mutation.ts +++ b/apps/client/src/app/api/comments/mutation.ts @@ -7,7 +7,9 @@ import { useToast } from "@/common/hook/useToast"; import { HTTP_ERROR_STATUS } from "@/shared/constant/api"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import type { HTTPError } from "ky"; +import { useParams } from "next/navigation"; import { commentQueryKey } from "./query"; +import type { CommentContent } from "./type"; export const useCommentMutation = (solutionId: number) => { const queryClient = useQueryClient(); @@ -19,9 +21,10 @@ export const useCommentMutation = (solutionId: number) => { await queryClient.invalidateQueries({ queryKey: commentQueryKey.all(), }); + showToast("댓글이 작성되었어요.", "success"); }, onError: () => { - showToast("댓글을 작성하는데 실패하였어요", "error"); + showToast("댓글 작성에 실패했어요", "error"); }, }); }; @@ -36,6 +39,7 @@ export const useDeleteCommentMutation = (solutionId: number) => { await queryClient.invalidateQueries({ queryKey: commentQueryKey.list(solutionId), }); + showToast("댓글이 삭제되었어요.", "success"); }, onError: (error: HTTPError) => { if (!error.response) return; @@ -47,26 +51,63 @@ export const useDeleteCommentMutation = (solutionId: number) => { }); }; -export const useEditCommentMutation = ( - solutionId: number, - commentId: number, -) => { +export const useEditCommentMutation = () => { const queryClient = useQueryClient(); + const params = useParams(); const { showToast } = useToast(); + const itemId = +params.id; return useMutation({ - mutationFn: (content: string) => editComment(commentId, content), - onSuccess: async () => { - await queryClient.invalidateQueries({ - queryKey: commentQueryKey.list(solutionId), - }); + mutationFn: ({ + commentId, + content, + }: { commentId: number; content: string }) => + editComment(commentId, content), + + onMutate: async (updatedComment) => { + const queryKey = commentQueryKey.list(itemId); + + await queryClient.cancelQueries({ queryKey }); + + const previousComments = + queryClient.getQueryData(queryKey); + + if (previousComments) { + const newComments = previousComments.map((comment) => + comment.commentId === updatedComment.commentId + ? { ...comment, content: updatedComment.content } + : comment, + ); + queryClient.setQueryData(queryKey, newComments); + } + + return { previousComments, queryKey }; }, - onError: (error: HTTPError) => { - if (!error.response) return; + + onError: (error: HTTPError, _variables, context) => { + if (context?.previousComments) { + queryClient.setQueryData(context.queryKey, context.previousComments); + } + + if (!error.response) { + showToast("댓글 수정에 실패했어요.", "error"); + return; + } + const { status } = error.response; if (status === HTTP_ERROR_STATUS.BAD_REQUEST) { - showToast("댓글 작성자가 아닙니다", "error"); + showToast("댓글 작성자가 아니거나, 요청이 잘못되었습니다.", "error"); + } else { + showToast("댓글 수정에 실패했어요.", "error"); } }, + + onSettled: () => { + queryClient.invalidateQueries({ queryKey: commentQueryKey.list(itemId) }); + }, + + onSuccess: () => { + showToast("댓글이 수정되었어요.", "success"); + }, }); }; diff --git a/apps/client/src/app/api/notices/mutation.ts b/apps/client/src/app/api/notices/mutation.ts index fca20a72..a2d15b94 100644 --- a/apps/client/src/app/api/notices/mutation.ts +++ b/apps/client/src/app/api/notices/mutation.ts @@ -9,6 +9,8 @@ import type { NoticeRequest } from "@/app/api/notices/type"; import { useToast } from "@/common/hook/useToast"; import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useParams } from "next/navigation"; +import type { CommentContent } from "../comments/type"; import { deleteNoticeAction, patchNoticeAction } from "./action"; export const useNoticeCommentMutation = (noticeId: number) => { @@ -21,29 +23,59 @@ export const useNoticeCommentMutation = (noticeId: number) => { await queryClient.invalidateQueries({ queryKey: noticeQueryKey.comments(noticeId), }); + showToast("댓글이 작성되었어요.", "success"); }, onError: () => { - showToast("댓글 작성에 실패했습니다.", "error"); + showToast("댓글 작성에 실패했어요.", "error"); }, }); }; -export const usePatchNoticeCommentMutation = ( - noticeId: number, - commentId: number, -) => { +export const usePatchNoticeCommentMutation = () => { const queryClient = useQueryClient(); + const params = useParams(); const { showToast } = useToast(); + const noticeId = +params.noticeId; return useMutation({ - mutationFn: (content: string) => patchNoticeComment(commentId, content), - onSuccess: async () => { - await queryClient.invalidateQueries({ - queryKey: noticeQueryKey.comments(noticeId), - }); + mutationFn: ({ + commentId, + content, + }: { commentId: number; content: string }) => + patchNoticeComment(commentId, content), + + onMutate: async (updatedComment) => { + const queryKey = noticeQueryKey.comments(noticeId); + await queryClient.cancelQueries({ queryKey }); + + const previousComments = + queryClient.getQueryData(queryKey); + + if (previousComments) { + const newComments = previousComments.map((comment) => + comment.commentId === updatedComment.commentId + ? { ...comment, content: updatedComment.content } + : comment, + ); + queryClient.setQueryData(queryKey, newComments); + } + + return { previousComments, queryKey }; }, - onError: () => { - showToast("댓글 수정에 실패했습니다.", "error"); + + onError: (_err, _variables, context) => { + showToast("댓글 수정에 실패했어요.", "error"); + if (context?.previousComments) { + queryClient.setQueryData(context.queryKey, context.previousComments); + } + }, + + onSettled: (_data, _error, _variables, context) => { + queryClient.invalidateQueries({ queryKey: context?.queryKey }); + }, + + onSuccess: () => { + showToast("댓글이 수정되었어요.", "success"); }, }); }; @@ -58,10 +90,10 @@ export const useDeleteNoticeCommentMutation = (noticeId: number) => { await queryClient.invalidateQueries({ queryKey: noticeQueryKey.comments(noticeId), }); - showToast("댓글이 삭제되었습니다.", "success"); + showToast("댓글이 삭제되었어요.", "success"); }, onError: () => { - showToast("댓글 삭제에 실패했습니다.", "error"); + showToast("댓글 삭제에 실패했어요.", "error"); }, }); }; diff --git a/apps/client/src/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/index.css.ts b/apps/client/src/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/index.css.ts index adefe254..b0b33a07 100644 --- a/apps/client/src/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/index.css.ts +++ b/apps/client/src/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/index.css.ts @@ -131,7 +131,7 @@ export const iconStyle = recipe({ export const listStyle = style({ width: "100%", - padding: "1.2rem 2.4rem 0 2.4rem", + padding: "1.2rem 0 0 2.4rem", overflowY: "scroll", ...scrollTheme.scrollbar, diff --git a/apps/client/src/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/index.tsx b/apps/client/src/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/index.tsx index 4d109f22..aba494a9 100644 --- a/apps/client/src/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/index.tsx +++ b/apps/client/src/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/index.tsx @@ -3,11 +3,11 @@ import { useDeleteNoticeCommentMutation, useDeleteNoticeMutation, useNoticeCommentMutation, + usePatchNoticeCommentMutation, usePatchNoticeMutation, } from "@/app/api/notices/mutation"; import { useNoticeCommentListQueryObject } from "@/app/api/notices/query"; import type { NoticeContent } from "@/app/api/notices/type"; -import { NoticeCommentsProvider } from "@/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/provider"; import { IcnClose, IcnEdit, IcnNew } from "@/asset/svg"; import Avatar from "@/common/component/Avatar"; import Textarea from "@/common/component/Textarea"; @@ -19,7 +19,13 @@ import { useQuery } from "@tanstack/react-query"; import clsx from "clsx"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; -import { type FormEvent, useEffect, useRef, useState } from "react"; +import { + type FormEvent, + useCallback, + useEffect, + useRef, + useState, +} from "react"; import { articleStyle, contentStyle, @@ -49,6 +55,7 @@ const NoticeDetail = ({ groupId, }: NoticeDetailProps) => { const { data: session } = useSession(); + const nickname = session?.user?.nickname; const router = useRouter(); const handleClose = () => { @@ -77,6 +84,14 @@ const NoticeDetail = ({ groupId, noticeId, ); + const { mutate: commentEditMutate } = usePatchNoticeCommentMutation(); + + const handleCommentEdit = useCallback( + (commentId: number, content: string) => { + commentEditMutate({ commentId, content }); + }, + [], + ); const handleSubmit = (e: FormEvent) => { e.preventDefault(); @@ -171,22 +186,16 @@ const NoticeDetail = ({
    - - {commentList?.map((item) => ( -
    - -
    - ))} -
    + {commentList?.map((commentContent) => ( +
    + +
    + ))}
void; - handleNoticeReset: () => void; - noticeId: number; -}; - -export const NoticeCommentsContext = createContext({} as Context); - -export const NoticeCommentsProvider = ({ - children, - noticeId, -}: { children: ReactNode; noticeId: number }) => { - const [noticeEditingItem, setNoticeEditingItem] = useState( - null, - ); - - const handleNoticeEditItem = (id: number) => { - setNoticeEditingItem((prev) => (prev === id ? null : id)); - }; - - const handleNoticeReset = () => { - setNoticeEditingItem(null); - }; - - return ( - - {children} - - ); -}; diff --git a/apps/client/src/common/component/Toast/index.css.ts b/apps/client/src/common/component/Toast/index.css.ts index e1459db1..92f9558b 100644 --- a/apps/client/src/common/component/Toast/index.css.ts +++ b/apps/client/src/common/component/Toast/index.css.ts @@ -15,7 +15,7 @@ export const containerStyle = style({ width: "100vw", - zIndex: theme.zIndex.top, + zIndex: 999, }); export const toastStyle = recipe({ diff --git a/apps/client/src/common/hook/useOutsideClick.ts b/apps/client/src/common/hook/useOutsideClick.ts index 0b853eca..adbfc765 100644 --- a/apps/client/src/common/hook/useOutsideClick.ts +++ b/apps/client/src/common/hook/useOutsideClick.ts @@ -3,12 +3,14 @@ import { useCallback, useEffect, useRef } from "react"; /** * @param callback toggle 관리용 setState 핸들러 + * @param mouseEvent mousedown(매우 빠름), click 이벤트 중 선택 (기본값: click) * @example * const callback = () => setShowMenu(false); const ref = useOutsideClick(callback); */ export const useOutsideClick = ( callback: () => void, + mouseEvent: "click" | "mousedown" = "click", ) => { const ref = useRef(null); const handleOutsideClick = useCallback( @@ -26,11 +28,10 @@ export const useOutsideClick = ( ); useEffect(() => { - // click대신 mousedown으로 빠르게 진행 - document.addEventListener("mousedown", handleOutsideClick); + document.addEventListener(mouseEvent, handleOutsideClick); document.addEventListener("keydown", handleESCKeyDown); return () => { - document.removeEventListener("mousedown", handleOutsideClick); + document.removeEventListener(mouseEvent, handleOutsideClick); document.removeEventListener("keydown", handleESCKeyDown); }; }, [handleOutsideClick]); diff --git a/apps/client/src/common/hook/usePrevious.ts b/apps/client/src/common/hook/usePrevious.ts new file mode 100644 index 00000000..2fcb57cc --- /dev/null +++ b/apps/client/src/common/hook/usePrevious.ts @@ -0,0 +1,13 @@ +import { useEffect, useRef } from "react"; +/** + * 이전 state 값을 반환하는 커스텀 훅 + * @param value - 저장할 state 값 + * @returns 이전 state 값 + */ +export const usePrevious = (value: T): T | undefined => { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }, [value]); + return ref.current; +}; diff --git a/apps/client/src/shared/component/CommentBox/CommentBox.stories.tsx b/apps/client/src/shared/component/CommentBox/CommentBox.stories.tsx index 86394328..3dd97eb2 100644 --- a/apps/client/src/shared/component/CommentBox/CommentBox.stories.tsx +++ b/apps/client/src/shared/component/CommentBox/CommentBox.stories.tsx @@ -1,4 +1,3 @@ -import { CommentsProvider } from "@/app/@modal/(...)group/[groupId]/solved-detail/components/CommentSection/provider"; import type { Meta } from "@storybook/react"; import { default as CommentBox } from "."; @@ -12,9 +11,7 @@ const meta: Meta = { decorators: [ (Story) => (
- - - +
), ], @@ -109,9 +106,10 @@ export const CommentList = { {data.map((item) => ( {}} + onCommentEdit={() => {}} /> ))} diff --git a/apps/client/src/shared/component/CommentBox/hook.ts b/apps/client/src/shared/component/CommentBox/hook.ts index 38ef43c3..209b9a5b 100644 --- a/apps/client/src/shared/component/CommentBox/hook.ts +++ b/apps/client/src/shared/component/CommentBox/hook.ts @@ -1,119 +1,116 @@ "use client"; - -import { CommentsContext } from "@/app/@modal/(...)group/[groupId]/solved-detail/components/CommentSection/provider"; -import { useEditCommentMutation } from "@/app/api/comments/mutation"; -import { usePatchNoticeCommentMutation } from "@/app/api/notices/mutation"; -import { NoticeCommentsContext } from "@/app/group/[groupId]/@modal/(.)notice/components/NoticeModal/NoticeDetail/provider"; -import { type KeyboardEvent, useContext } from "react"; +import { usePrevious } from "@/common/hook/usePrevious"; +import { useAtomValue, useSetAtom } from "jotai"; +import { type KeyboardEvent, useEffect } from "react"; import { flushSync } from "react-dom"; import { type SubmitHandler, useForm } from "react-hook-form"; +import { + clearEditingItemAtom, + editingItemIdAtom, + setEditingItemAtom, +} from "./store"; type EditForm = { input: string; }; +type EditFormProps = { + commentId: number; + defaultValue: string; + onCommentEdit: (commentId: number, content: string) => void; +}; -export const useEditForm = (commentId: number, defaultValue: string) => { - const { editingItem, handleEditItem, handleReset, solutionId } = - useContext(CommentsContext); - +export const useEditComment = ({ + commentId, + defaultValue, + onCommentEdit, +}: EditFormProps) => { + const currentEditingItemId = useAtomValue(editingItemIdAtom); + const isCurrentEditing = currentEditingItemId === commentId; + const setCurrentEditItem = useSetAtom(setEditingItemAtom); + const clearCurrentEditItem = useSetAtom(clearEditingItemAtom); + const prevEditingItemId = usePrevious(currentEditingItemId); const { - noticeEditingItem, - handleNoticeEditItem, - handleNoticeReset, - noticeId, - } = useContext(NoticeCommentsContext); - - const { register, setValue, setFocus, handleSubmit } = useForm({ + register, + formState: { isDirty }, + resetField, + reset, + setFocus, + handleSubmit: _handleSubmit, + } = useForm({ defaultValues: { input: defaultValue, }, }); - const { mutate: editMutate } = useEditCommentMutation(solutionId, commentId); - const { mutate: noticeEditMutate } = usePatchNoticeCommentMutation( - noticeId, - commentId, - ); - - const isEditing = editingItem === commentId; - const isNoticeEditing = noticeEditingItem === commentId; - - const handleDetailEditBtnClick = () => { - flushSync(() => { - handleEditItem(commentId); - }); - - setFocus("input"); + const applyEdit: SubmitHandler = ({ input }) => { + if (isDirty) { + onCommentEdit(commentId, input); + resetField("input", { + keepDirty: false, + defaultValue: input, + }); + } }; - const handleDetailEditSubmit: SubmitHandler = (data) => { - editMutate(data.input); + const handleSubmit = _handleSubmit(applyEdit); - handleReset(); + const handleEdit = () => { + if (!isCurrentEditing) return; + handleSubmit(); + clearCurrentEditItem(); }; - const handleDetailTextAreaKeyDown = (e: KeyboardEvent) => { - e.stopPropagation(); + useEffect(() => { + if (prevEditingItemId === commentId && !isCurrentEditing) { + handleSubmit(); + } + }, [ + currentEditingItemId, + prevEditingItemId, + isCurrentEditing, + commentId, + handleSubmit, + ]); - if (e.key === "Enter" && !e.shiftKey) { - handleSubmit(handleDetailEditSubmit)(); + const handleEditBtnClick = () => { + if (isCurrentEditing) { + handleEdit(); + return; } + // isEditing 상태로 전환 시 즉시 포커스 이동을 위해 flushSync 사용 + // form, textarea는 isEditing 상태에 따라 조건부로 렌더링되기 때문에 사용됨 + flushSync(() => { + setCurrentEditItem(commentId); + }); - if (e.key === "Escape") { - handleReset(); + setFocus("input"); + }; - setValue("input", defaultValue); - } + const handleEditCancel = () => { + reset(); + clearCurrentEditItem(); }; - const handleNoticeTextAreaKeyDown = (e: KeyboardEvent) => { + const handleTextAreaKeyDown = (e: KeyboardEvent) => { e.stopPropagation(); if (e.key === "Enter" && !e.shiftKey) { - handleSubmit(handleNoticeEditSubmit)(); + handleEditBtnClick(); + clearCurrentEditItem(); } if (e.key === "Escape") { - handleNoticeReset(); - - setValue("input", defaultValue); + handleEditCancel(); } }; - const handleNoticeEditBtnClick = () => { - flushSync(() => { - handleNoticeEditItem(commentId); - }); - - setFocus("input"); - }; - - const handleNoticeEditSubmit: SubmitHandler = (data) => { - noticeEditMutate(data.input); - - handleNoticeReset(); - }; - - const handleDetailHookFormSubmit = handleSubmit(handleDetailEditSubmit); - const handleNoticeHookFormSubmit = handleSubmit(handleNoticeEditSubmit); - - const control = { - detail: { - isEditing, - handleEditBtnClick: handleDetailEditBtnClick, - handleTextAreaKeyDown: handleDetailTextAreaKeyDown, - handleHookFormSubmit: handleDetailHookFormSubmit, - }, - notice: { - isEditing: isNoticeEditing, - handleEditBtnClick: handleNoticeEditBtnClick, - handleTextAreaKeyDown: handleNoticeTextAreaKeyDown, - handleHookFormSubmit: handleNoticeHookFormSubmit, - }, - }; - return { register, - control, + isEditing: isCurrentEditing, + handleEdit, + handleEditBtnClick, + handleEditCancel, + handleTextAreaKeyDown, + handleSubmit, }; }; diff --git a/apps/client/src/shared/component/CommentBox/index.css.ts b/apps/client/src/shared/component/CommentBox/index.css.ts index eb4e6e4c..7240f24f 100644 --- a/apps/client/src/shared/component/CommentBox/index.css.ts +++ b/apps/client/src/shared/component/CommentBox/index.css.ts @@ -43,22 +43,11 @@ export const contentStyle = style({ whiteSpace: "pre-line", }); -export const contentWrapperStyle = recipe({ - base: { - display: "flex", - flexDirection: "column", - width: "100%", - }, - variants: { - variant: { - notice: { - gap: "0.8rem", - }, - detail: { - gap: "1.6rem", - }, - }, - }, +export const contentWrapperStyle = style({ + display: "flex", + flexDirection: "column", + width: "100%", + gap: "0.8rem", }); export const topContentStyle = style({ diff --git a/apps/client/src/shared/component/CommentBox/index.tsx b/apps/client/src/shared/component/CommentBox/index.tsx index 30a0ccda..ccc94a4e 100644 --- a/apps/client/src/shared/component/CommentBox/index.tsx +++ b/apps/client/src/shared/component/CommentBox/index.tsx @@ -5,8 +5,9 @@ import { textareaEditStyle } from "@/app/group/[groupId]/@modal/(.)notice/compon import { IcnClose, IcnEdit } from "@/asset/svg"; import Avatar from "@/common/component/Avatar"; import Textarea from "@/common/component/Textarea"; +import { useOutsideClick } from "@/common/hook/useOutsideClick"; import { formatDistanceDate } from "@/common/util/date"; -import { useEditForm } from "@/shared/component/CommentBox/hook"; +import { useEditComment } from "@/shared/component/CommentBox/hook"; import { containerStyle, contentStyle, @@ -20,48 +21,58 @@ import { } from "@/shared/component/CommentBox/index.css"; import useA11yHoverHandler from "@/shared/hook/useA11yHandler"; import clsx from "clsx"; -import { useState } from "react"; -type CommentBoxProps = CommentContent & { - variant: "detail" | "notice"; - onDelete?: (commentId: number) => void; +type CommentBoxProps = { + commentContent: CommentContent; className?: string; - isMine?: boolean; + isMine: boolean; + onDelete: (commentId: number) => void; + onCommentEdit: (itemId: number, content: string) => void; }; const CommentBox = ({ - variant, - commentId, - writerNickname, - writerProfileImage, - content, - createdAt, - onDelete, + commentContent: { + commentId, + content, + createdAt, + writerNickname, + writerProfileImage, + }, className, isMine, + onDelete, + onCommentEdit, }: CommentBoxProps) => { const { isActive, ...handlers } = useA11yHoverHandler(); - - const { register, control } = useEditForm(commentId, content); - - const [isCommentEdit, setIsCommentEdit] = useState(false); - const { + register, isEditing, - handleEditBtnClick: _handleEditBtnClick, - handleHookFormSubmit, + handleEdit, + handleEditBtnClick, + handleEditCancel, handleTextAreaKeyDown, - } = control[variant]; + handleSubmit, + } = useEditComment({ + commentId, + defaultValue: content, + onCommentEdit, + }); + const ref = useOutsideClick(handleEdit); - const handleEditBtnClick = () => { - _handleEditBtnClick(); - setIsCommentEdit(!isCommentEdit); + const handleDeleteBtnClick = () => { + if (isEditing) { + handleEditCancel(); + return; + } + onDelete(commentId); }; + const editTitle = isEditing ? "댓글 수정 완료하기" : "댓글 수정하기"; + const deleteTitle = isEditing ? "댓글 수정 취소하기" : "댓글 삭제하기"; return (
  • -
    +

    {writerNickname}

    -

    {formatDistanceDate(createdAt)}

    +
    {isEditing ? ( - +