diff --git a/src/api/Auth/authApi.ts b/src/api/Auth/authApi.ts index 51ef177..8b151ac 100644 --- a/src/api/Auth/authApi.ts +++ b/src/api/Auth/authApi.ts @@ -15,3 +15,13 @@ export const logoutRequest = async () => { throw new Error(message); } }; + +export const signoutRequest = async () => { + try { + const response = await apiClient.post(API_ENDPOINTS.AUTH.SIGN_OUT); + return response.data; + } catch (error: unknown) { + const message = getErrorMessage(error, "회원 탈퇴 실패"); + throw new Error(message); + } +}; diff --git a/src/components/Common/Button.tsx b/src/components/Common/Button.tsx index 4ce2151..8f3be6a 100644 --- a/src/components/Common/Button.tsx +++ b/src/components/Common/Button.tsx @@ -7,7 +7,7 @@ interface ButtonProps { onClick: () => void; type?: "button" | "submit" | "reset"; disabled?: boolean; - design?: 1 | 2 | 3; + design?: 1 | 2 | 3 | 4; fontColor?: 1 | 2; fontWeight?: 100 | 400 | 700; title?: string; @@ -38,10 +38,20 @@ const buttonStyles = { background-color: rgba(0, 0, 0, 0.25); color: ${({ theme }) => theme.colors.text_B}; border-radius: 25px; + &:hover { background-color: rgba(0, 0, 0, 0.45); } `, + 4: css` + background-color: ${({ theme }) => theme.colors.danger}; + color: ${({ theme }) => theme.colors.text_B}; + border-radius: 6px; + + &:hover { + background-color: #c62828; + } + `, }; const buttonFontColors = { @@ -54,7 +64,7 @@ const buttonFontColors = { }; const StyledButton = styled.button<{ - design: 1 | 2 | 3; + design: 1 | 2 | 3 | 4; fontColor?: 1 | 2; fontWeight: 100 | 400 | 700; }>` diff --git a/src/constants/endpoints.ts b/src/constants/endpoints.ts index 6b1c9dd..ba4d96a 100644 --- a/src/constants/endpoints.ts +++ b/src/constants/endpoints.ts @@ -5,7 +5,7 @@ export const API_ENDPOINTS = { LOGOUT: "/student/auth/logout", // Delete GOOGLE_LOGIN: "/auth/sign-in/google", //post REFRESH: "/auth/refresh/token", // post - SIGN_OUT: "/auth/sign-out/google", // post + SIGN_OUT: "/student/auth/sign-out/google", // post CHECK_NICKNAME: "/student/member/check/nickname", // post }, diff --git a/src/pages/UserMypage/UpdateUserInfoPage.tsx b/src/pages/UserMypage/UpdateUserInfoPage.tsx index 274344a..d25b3f0 100644 --- a/src/pages/UserMypage/UpdateUserInfoPage.tsx +++ b/src/pages/UserMypage/UpdateUserInfoPage.tsx @@ -4,14 +4,18 @@ import { useNavigate } from "react-router-dom"; import { PAGE_PATHS } from "../../constants/pagePaths"; import { useDispatch, useSelector } from "react-redux"; import type { AppDispatch, RootState } from "../../redux/store"; - +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; import { checkNicknameAPI, signupAPI } from "../../api/Signup/signupAPI"; +import { signoutRequest } from "../../api/Auth/authApi"; import type { UserCategoriesList } from "../../types/authInfo"; import Button from "../../components/Common/Button"; import CategorySelect from "../../components/Signup/CategorySelect"; import InfoCheckModal from "../../components/Signup/signupModal"; import RoleSelect from "../../components/Signup/RoleSelect"; -import { setUserInfo } from "../../redux/Auth/authSlice"; +import { resetUserInfo, setUserInfo } from "../../redux/Auth/authSlice"; +import { theme } from "../../assets/styles/theme"; +import { logoutUsingToken } from "../../redux/token/tokenSlice"; const PageWrapper = styled.div` display: flex; @@ -105,6 +109,133 @@ const RoleSection = styled.div` gap: 30px; `; +const ModalBackdrop = styled.div` + position: fixed; + inset: 0; + background: ${({ theme }) => theme.colors.background_Overlay}; + display: flex; + justify-content: center; + align-items: center; + z-index: ${({ theme }) => theme.zIndex.modal}; +`; + +const ModalContent = styled.div<{ isSuccess: boolean }>` + background: #fff; + width: ${({ theme }) => theme.size.modal.width}; + padding: 2rem 1.5rem; + border-radius: 16px; + box-shadow: ${({ theme }) => theme.shadow.lg}; + text-align: center; + animation: fadeIn 0.3s ${({ theme }) => theme.transition.default}; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + + h2 { + font-size: ${({ theme }) => theme.fontSize.title.max}; + color: ${({ isSuccess, theme }) => + isSuccess ? theme.colors.success : theme.colors.danger}; + margin: 0; + display: flex; + align-items: center; + gap: 0.5rem; + } + + p { + font-size: ${({ theme }) => theme.fontSize.body.max}; + color: ${({ theme }) => theme.colors.text_D}; + margin: 0; + white-space: pre-line; + } +`; +const Input = styled.input` + width: 100%; + padding: 0.75rem 1rem; + font-size: 1rem; + border: 1px solid #ccc; + border-radius: 6px; + outline: none; + + &:focus { + border-color: ${({ theme }) => theme.colors.header || "#333"}; + } +`; + +const Modal: React.FC<{ + message: string; + onClose: () => void; + navigate: () => void; +}> = ({ message, onClose, navigate }) => { + const [inputValue, setInputValue] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const handleConfirm = async () => { + if (inputValue !== "탈퇴하겠습니다" || isLoading) return; + + setIsLoading(true); + try { + await navigate(); + } finally { + onClose(); + setIsLoading(false); + } + }; + + return ( + + e.stopPropagation()} isSuccess={false}> +
+ +
+

+ {message} +

+ + setInputValue(e.target.value)} + disabled={isLoading} + /> + +
+
+
+
+ ); +}; + const ButtonSection = styled.div``; const UpdateUserInfoPage = () => { @@ -116,6 +247,7 @@ const UpdateUserInfoPage = () => { const [nicknameCheckMessage, setNicknameCheckMessage] = useState(""); // 중복확인 결과 메세지 const [nicknameCheck, setNicknameCheck] = useState(null); // 중복확인 여부 확인 const [isCheckingNickname, setIsCheckingNickname] = useState(false); // api 중복 실행 방지 + const [showSignOutModal, setShowSignOutModal] = useState(false); const [selectedCategories, setSelectedCategories] = useState< UserCategoriesList[] @@ -303,6 +435,12 @@ const UpdateUserInfoPage = () => { fontWeight={400} /> +