From 34bb9bab9b6057a68a23f66d93d20b8bfbb0bf63 Mon Sep 17 00:00:00 2001 From: Gyuri Date: Fri, 30 Jan 2026 17:32:44 +0900 Subject: [PATCH] feat(User): user delete --- src/api/authApi.js | 7 ++- src/components/modal/DeleteUserModal.jsx | 58 ++++++++++++++++++++++++ src/components/pages/mypage/Links.jsx | 8 +++- src/constants/color.js | 2 + src/hooks/AuthHooks.js | 32 ++++++++++++- src/hooks/CreateBookHooks.js | 12 +++-- src/pages/Mypage.jsx | 11 +++++ 7 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 src/components/modal/DeleteUserModal.jsx diff --git a/src/api/authApi.js b/src/api/authApi.js index d4a41ee..5326a2d 100644 --- a/src/api/authApi.js +++ b/src/api/authApi.js @@ -1,4 +1,4 @@ -import { get, patch } from './base'; +import { get, patch, del } from './base'; import { AUTH_URL } from '../constants/api'; export const getOAuthKakaoApi = async (code) => { @@ -14,4 +14,9 @@ export const getUserInfoApi = async () => { export const patchUserNicknameApi = async (data) => { const response = await patch(AUTH_URL.nickname(data)); return response; +}; + +export const deleteUserApi = async () => { + const response = await del(AUTH_URL.deleteUser); + return response; }; \ No newline at end of file diff --git a/src/components/modal/DeleteUserModal.jsx b/src/components/modal/DeleteUserModal.jsx new file mode 100644 index 0000000..316f00c --- /dev/null +++ b/src/components/modal/DeleteUserModal.jsx @@ -0,0 +1,58 @@ +import Modal from "./Modal"; +import styled from "styled-components"; +import { Button, Div, Text } from "../common/div"; +import { GRAY5, GRAY1, BLACK, RED } from "../../constants/color"; + +const Container = styled.div` + width: 260px; + padding: 22px; + background-color: #fff; + border-radius: 8px; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); + top: 50%; + transform: translateY(-50%); +`; + +const StyledButton = styled(Button)` + border-radius: 4px; + flex-grow: 1; + height: 30px; + line-height: 30px; + padding: 0; +`; + +export const DeleteUserModal = ({ isOpen, closeModal, handleDeleteUser }) => { + return ( + + + + 회원 탈퇴 + + + 정말 탈퇴하시겠습니까? + + + 모든 데이터가 삭제되며 복구할 수 없습니다. + +
+ + 취소 + + + 탈퇴 + +
+
+
+ ) +} + + + diff --git a/src/components/pages/mypage/Links.jsx b/src/components/pages/mypage/Links.jsx index 349abc5..f911ce4 100644 --- a/src/components/pages/mypage/Links.jsx +++ b/src/components/pages/mypage/Links.jsx @@ -1,7 +1,7 @@ import styled from "styled-components"; import { Link } from "react-router-dom"; import { Div, Text } from "../../common/div"; -import { BLACK, GRAY5 } from "../../../constants/color"; +import { BLACK, GRAY5, RED } from "../../../constants/color"; import { GoIcon } from "../../../assets/icons"; const LinkButton = styled(Link)` @@ -13,7 +13,7 @@ const LinkButton = styled(Link)` height: 26px; `; -const Links = ({ logout }) => { +const Links = ({ logout, handleShowDeleteUser }) => { const handleClick = () => { window.location.href = 'https://www.notion.so/MUSEE-11b0cdd700f78074b473f3759010de3e?pvs=4'; } @@ -32,6 +32,10 @@ const Links = ({ logout }) => { 로그아웃 + + 회원 탈퇴 + + ) } diff --git a/src/constants/color.js b/src/constants/color.js index ea97628..1acb326 100644 --- a/src/constants/color.js +++ b/src/constants/color.js @@ -12,5 +12,7 @@ export const BLACK = '#3C3C3C'; export const LIGHT_BLUE = '#E2EFFF'; export const BLUE = '#0A7AFF'; +export const RED = '#FF3B30'; + export const DIM = 'rgba(0, 0, 0, 0.4)'; export const DIM2 = 'rgba(0, 0, 0, 0.8)'; diff --git a/src/hooks/AuthHooks.js b/src/hooks/AuthHooks.js index d326c33..4b5998a 100644 --- a/src/hooks/AuthHooks.js +++ b/src/hooks/AuthHooks.js @@ -1,6 +1,6 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom" -import { getOAuthKakaoApi, getUserInfoApi, patchUserNicknameApi } from "../api/authApi"; +import { getOAuthKakaoApi, getUserInfoApi, patchUserNicknameApi, deleteUserApi } from "../api/authApi"; import { useDispatch, useSelector } from "react-redux"; import { login, setToken, logout, setName } from "../store/slices/userSlice"; import { ToastMessage } from "../components/common/Toast"; @@ -10,6 +10,7 @@ export const useAuth = () =>{ const dispatch = useDispatch(); const { username, userTier } = useSelector((state) => state.user.value); const [isShowNicknameEdit, setIsShowNicknameEdit] = useState(false); + const [isShowDeleteUser, setIsShowDeleteUser] = useState(false); const [nickname, setNickname] = useState(username); const nicknameDisabled = nickname === username; @@ -61,10 +62,36 @@ export const useAuth = () =>{ } } + const handleShowDeleteUser = () => { + setIsShowDeleteUser(true); + } + + const handleCloseDeleteUser = () => { + setIsShowDeleteUser(false); + } + + const handleDeleteUser = async () => { + try { + const response = await deleteUserApi(); + if (response.status === 200) { + dispatch(logout()); + ToastMessage.info('회원 탈퇴가 완료되었습니다.'); + navigate("/login"); + } else { + console.error(response); + ToastMessage.info('회원 탈퇴에 실패했습니다.'); + } + } catch (error) { + console.error(error); + ToastMessage.info('회원 탈퇴에 실패했습니다.'); + } + } + return { username, userTier, isShowNicknameEdit, + isShowDeleteUser, nickname, nicknameDisabled, setNickname, @@ -73,5 +100,8 @@ export const useAuth = () =>{ handleShowNicknameEdit, handleNicknameEdit, handleCloseNicknameEdit, + handleShowDeleteUser, + handleCloseDeleteUser, + handleDeleteUser, } } \ No newline at end of file diff --git a/src/hooks/CreateBookHooks.js b/src/hooks/CreateBookHooks.js index 2841a43..a0fff96 100644 --- a/src/hooks/CreateBookHooks.js +++ b/src/hooks/CreateBookHooks.js @@ -82,7 +82,7 @@ export const useCreateBook = (id) => { const handleFileChange = (e) => { const fileArr = Array.from(e.target.files); - const currentPhotoCount = editData?.photos?.length || 0; // 기존 사진 개수 + const currentPhotoCount = (editData?.photos?.length || 0) + previewImages.length; // 기존 사진 + 이미 업로드된 사진 개수 const maxNewPhotos = 3 - currentPhotoCount; // 추가 가능한 사진 개수 // 선택된 파일이 제한을 초과하는 경우 제한만큼만 선택 @@ -92,9 +92,13 @@ export const useCreateBook = (id) => { alert(`최대 ${3}장까지만 업로드할 수 있습니다. ${maxNewPhotos}장만 선택되었습니다.`); } - setUploadPhotos(limitedFiles); - const fileURLs = limitedFiles.map(file => URL.createObjectURL(file)); - setPreviewImages(fileURLs); + // 기존 업로드된 파일들과 새로운 파일들을 합침 + const newUploadPhotos = [...uploadPhotos, ...limitedFiles]; + setUploadPhotos(newUploadPhotos); + + // 기존 미리보기 이미지들과 새로운 이미지들을 합침 + const newFileURLs = limitedFiles.map(file => URL.createObjectURL(file)); + setPreviewImages([...previewImages, ...newFileURLs]); // 파일 input 초기화 (같은 파일을 다시 선택할 수 있도록) e.target.value = ''; diff --git a/src/pages/Mypage.jsx b/src/pages/Mypage.jsx index 80a6270..c540ba4 100644 --- a/src/pages/Mypage.jsx +++ b/src/pages/Mypage.jsx @@ -5,6 +5,7 @@ import Navigation from "../components/common/Navigation"; import Profile from "../components/pages/mypage/Profile"; import Links from "../components/pages/mypage/Links"; import { NicknameEditModal }from "../components/modal/NicknmaeEdiModal"; +import { DeleteUserModal } from "../components/modal/DeleteUserModal"; import { useAuth } from "../hooks/AuthHooks"; const Mypage = () => { @@ -12,6 +13,7 @@ const Mypage = () => { username, userTier, isShowNicknameEdit, + isShowDeleteUser, nickname, nicknameDisabled, setNickname, @@ -19,6 +21,9 @@ const Mypage = () => { handleShowNicknameEdit, handleNicknameEdit, handleCloseNicknameEdit, + handleShowDeleteUser, + handleCloseDeleteUser, + handleDeleteUser, } = useAuth(); return ( <> @@ -31,6 +36,7 @@ const Mypage = () => { /> { setNickname={setNickname} nicknameDisabled={nicknameDisabled} /> + )