diff --git a/src/api/auth.ts b/src/api/auth.ts index 681ad4c..eb86074 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -60,3 +60,7 @@ export const passwordResetConfirm = (uidb64: string, token: string, password: st }; return axiosInstance.post(`/auth/password/reset/`, requestForm); }; + +export const deleteAccount = () => { + return axiosInstance.delete("/auth/delete/"); +}; diff --git a/src/common/comment/CommentInput.tsx b/src/common/comment/CommentInput.tsx index a1028f0..4d86d8e 100644 --- a/src/common/comment/CommentInput.tsx +++ b/src/common/comment/CommentInput.tsx @@ -40,6 +40,10 @@ const CommentInput = ({ onClose = () => {}, articleId, onCommentSubmit, toast }: }, [setFocus]); const onSubmit = async (data: CommentFormData) => { + if (!data.content.trim()) { + toast("내용을 입력해주세요."); + return; + } const formData = new FormData(); formData.append("content", data.content); @@ -53,9 +57,11 @@ const CommentInput = ({ onClose = () => {}, articleId, onCommentSubmit, toast }: onCommentSubmit(newComment); } catch (error) { if (error instanceof AxiosError && error.response) { - console.log("훈수작성 실패", error); + console.error("훈수작성 실패", error); if (error.response.status === 401) { toast("로그인 후 사용가능합니다."); + } else if (error.response.status === 400) { + toast("잘못된 요청입니다. 다시 시도해주세요."); } } } diff --git a/src/common/info/InfoMyPageLeft.tsx b/src/common/info/InfoMyPageLeft.tsx index 78c21a2..508b0e4 100644 --- a/src/common/info/InfoMyPageLeft.tsx +++ b/src/common/info/InfoMyPageLeft.tsx @@ -5,6 +5,8 @@ import ProfileImage from "../profile/ProfileImage"; import { useUserStore } from "../../config/store"; import { userInfoImageUpdate, userInfoUpdate } from "../../api/account"; import { AxiosError } from "axios"; +import { ModalPortalModal } from "../../config/ModalPortalModal"; +import ModalUserDelete from "../modal/ModalUserDelete"; type InfoMyPageLeftProps = { isUserMypage: boolean; @@ -17,6 +19,7 @@ const InfoMyPageLeft = ({ isUserMypage }: InfoMyPageLeftProps) => { const [profileImage, setProfileImage] = useState(null); const { user, updateUser, otherUser } = useUserStore(); const [error, setError] = useState(null); + const [deleteUserModal, setDeleteUserModal] = useState(false); useEffect(() => { if (user.nickname) setNicknameText(user.nickname); @@ -55,11 +58,19 @@ const InfoMyPageLeft = ({ isUserMypage }: InfoMyPageLeftProps) => { } }; + const handleDeleteUserModalClose = () => { + setDeleteUserModal(false); + }; + + const handleDeleteUserModalOpen = () => { + setDeleteUserModal(true); + }; + return (
@@ -146,10 +157,27 @@ const InfoMyPageLeft = ({ isUserMypage }: InfoMyPageLeftProps) => {

{error}

{isUserMypage && ( - + <> + + + )} + + {deleteUserModal && ( + + )} +
); }; diff --git a/src/common/modal/ModalUserDelete.tsx b/src/common/modal/ModalUserDelete.tsx new file mode 100644 index 0000000..e2ff7ee --- /dev/null +++ b/src/common/modal/ModalUserDelete.tsx @@ -0,0 +1,164 @@ +import { AnimatePresence, motion } from "framer-motion"; +import { useEffect, useRef, useState } from "react"; +import { IoClose } from "react-icons/io5"; +import Button from "../button/Button"; +import { authApi } from "../../api"; +import { useNavigate } from "react-router-dom"; +import { IoWarning } from "react-icons/io5"; + +interface ModalUserDeleteProps { + onClose: () => void; + isOpen: boolean; + parent: string; +} + +const ModalUserDelete = ({ onClose, isOpen, parent }: ModalUserDeleteProps) => { + const modalRef = useRef(null); + const [isAlert, setIsAlert] = useState(false); + const [alertMsg, setAlertMsg] = useState(null); + useEffect(() => { + if (isOpen) { + modalRef.current?.focus(); + } + }, [isOpen]); + + const handleClose = async () => { + onClose(); + }; + + const navigate = useNavigate(); + + useEffect(() => { + const parentElement = document.querySelector("." + parent); + const headerElement = document.querySelector(".header"); + + let scrollY = 0; + + if (isOpen) { + //? 현재 스크롤 위치 저장 + scrollY = window.scrollY; + + //? 스크롤을 0으로 설정 (모달이 열릴 때) + window.scrollTo(0, 0); + + //? 스크롤을 고정하고 화면을 고정 + document.body.style.overflowY = "hidden"; + document.body.style.top = `-${scrollY}px`; + document.body.style.width = "100%"; + + //? 부모 및 헤더에 blur 효과 추가 + parentElement?.classList.add("blur-[2px]"); + headerElement?.classList.add("blur-[2px]"); + } + + return () => { + //? 모달이 닫힐 때 블러 제거 + parentElement?.classList.remove("blur-[2px]"); + headerElement?.classList.remove("blur-[2px]"); + + //? 저장된 스크롤 위치로 복원 + const storedScrollY = parseInt(document.body.style.top || "0") * -1; + + //? 스크롤 및 위치 복원 + document.body.style.overflowY = "scroll"; + document.body.style.top = ""; + document.body.style.width = ""; + + //? 저장된 스크롤 위치로 이동 + window.scrollTo(0, storedScrollY); + }; + }, [isOpen, parent]); + + const handleUserAccountDelete = async () => { + try { + await authApi.deleteAccount(); + navigate("/"); + } catch (error) { + console.error("회원 탈퇴 실패:", error); + alertHandler("문제가 발생했습니다. 다시 시도해주세요."); + } + }; + + const alertHandler = (text: string) => { + setIsAlert(true); + setAlertMsg(text); + }; + + useEffect(() => { + if (isAlert) { + const timer = setTimeout(() => { + setIsAlert(false); + setAlertMsg(null); + }, 2000); + + return () => clearTimeout(timer); + } + }, [isAlert]); + + return ( + <> + +
+ { + if (e.key === "Escape") onClose(); + }} + initial={{ opacity: 0, translateY: 20 }} + animate={{ opacity: [1], translateY: 0 }} + exit={{ opacity: 0 }} + onClick={(e) => e.stopPropagation()} + className="dark:bg-gray-900 outline-none w-full h-full md:w-[570px] md:h-[240px] md:rounded-3xl bg-white relative flex justify-center items-center" + > +
+
+ 회원 탈퇴 +
+
+ 정말 탈퇴 하시겠습니까? +
+
+ + +
+
+ + +
+ + {isAlert && ( + + +
{alertMsg}
+
+ )} +
+
+ + ); +}; + +export default ModalUserDelete; diff --git a/src/pages/MyPage.tsx b/src/pages/MyPage.tsx index 5d840d4..d5c82ff 100644 --- a/src/pages/MyPage.tsx +++ b/src/pages/MyPage.tsx @@ -9,9 +9,9 @@ import { useNavigate, useParams } from "react-router-dom"; import { AxiosError } from "axios"; const MyPage = () => { - const [isUserMypage, setIsUserMypage] = useState(false); //마이페이지에 들어온 유저가 본인인지 아닌지 확인할거 + const [isUserMypage, setIsUserMypage] = useState(false); const { user, updateUser, initOtherUser } = useUserStore(); - const { userId } = useParams(); // 유저 클릭해서 마이페이지 보는 경우 볼려는 유저의 아이디 + const { userId } = useParams(); const navigate = useNavigate(); useEffect(() => {