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}
/>
+