diff --git a/src/assets/notFoundImg.svg b/src/assets/notFoundImg.svg
new file mode 100644
index 00000000..f92e4a47
--- /dev/null
+++ b/src/assets/notFoundImg.svg
@@ -0,0 +1,1699 @@
+
diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx
index 7935df6a..3113736d 100644
--- a/src/components/common/header/Header.tsx
+++ b/src/components/common/header/Header.tsx
@@ -10,9 +10,12 @@ import { useMyProfileInfo } from '../../../hooks/useMyInfo';
import DefaultImg from '../../../assets/defaultImg.png';
import { UserCircleIcon } from '@heroicons/react/24/outline';
import loadingImg from '../../../assets/loadingImg.svg';
+import { useModal } from '../../../hooks/useModal';
+import Modal from '../modal/Modal';
function Header() {
- const { userLogout } = useAuth();
+ const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
+ const { userLogout } = useAuth(handleModalOpen);
const { isLoggedIn } = useAuthStore((state) => state);
const { myData, isLoading } = useMyProfileInfo();
@@ -64,6 +67,9 @@ function Header() {
>
+
+ {message}
+
);
}
diff --git a/src/components/common/modal/Modal.styled.ts b/src/components/common/modal/Modal.styled.ts
index 527f4817..dd0afcf3 100644
--- a/src/components/common/modal/Modal.styled.ts
+++ b/src/components/common/modal/Modal.styled.ts
@@ -41,28 +41,45 @@ export const ModalCloseButton = styled.button`
position: absolute;
top: 0;
right: 0;
- padding: 12px;
+ margin: 0.8rem;
+ background: #ccc;
+ border-radius: 50%;
+ padding: 0.1rem;
svg {
- color: #fff;
+ color: ${({ theme }) => theme.color.white};
width: 20px;
height: 20px;
}
`;
+export const ModalIconWrapper = styled.div`
+ margin-bottom: 1rem;
+
+ svg {
+ color: #385a91;
+ width: 40px;
+ height: 40px;
+ }
+`;
+
export const ModalContents = styled.p`
- font-size: 2rem;
+ font-size: 1.1rem;
font-weight: 600;
- color: ${({ theme }) => theme.color.white};
+ color: #393939;
+ text-align: center;
`;
export const ModalBody = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
- padding: 56px 32px 32px;
+ padding: 4rem 3rem;
border-radius: ${({ theme }) => theme.borderRadius.primary};
- background-color: ${({ theme }) => theme.color.navy};
+ background-color: ${({ theme }) => theme.color.white};
max-width: 80%;
`;
diff --git a/src/components/common/modal/Modal.tsx b/src/components/common/modal/Modal.tsx
index 2817029d..eb3b086d 100644
--- a/src/components/common/modal/Modal.tsx
+++ b/src/components/common/modal/Modal.tsx
@@ -1,6 +1,6 @@
import { useRef, useState } from 'react';
import { createPortal } from 'react-dom';
-import { XMarkIcon } from '@heroicons/react/24/outline';
+import { XMarkIcon, CheckCircleIcon } from '@heroicons/react/24/outline';
import * as S from './Modal.styled';
import ScrollPreventor from './ScrollPreventor';
interface ModalProps {
@@ -43,6 +43,9 @@ const Modal = ({ children, isOpen, onClose }: ModalProps) => {
+
+
+
{children}
diff --git a/src/components/common/page/notFoundPage/NotFoundPage.styled.ts b/src/components/common/page/notFoundPage/NotFoundPage.styled.ts
new file mode 100644
index 00000000..27844ab5
--- /dev/null
+++ b/src/components/common/page/notFoundPage/NotFoundPage.styled.ts
@@ -0,0 +1,34 @@
+import styled from 'styled-components';
+import notFoundImg from '../../../../assets/notFoundImg.svg';
+
+export const Container = styled.div`
+ width: 100vw;
+ height: 100vh;
+ background-image: url(${notFoundImg});
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+
+ position: relative;
+`;
+
+export const BackButton = styled.button`
+ position: absolute;
+ bottom: 15%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+
+ padding: 10px 20px;
+ font-size: 1rem;
+ font-weight: 500;
+ color: white;
+ background-color: #3e404d;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: background-color 0.3s;
+
+ &:hover {
+ background-color: #0056b3;
+ }
+`;
diff --git a/src/components/common/page/notFoundPage/NotFoundPage.tsx b/src/components/common/page/notFoundPage/NotFoundPage.tsx
new file mode 100644
index 00000000..6b8d85d6
--- /dev/null
+++ b/src/components/common/page/notFoundPage/NotFoundPage.tsx
@@ -0,0 +1,17 @@
+import { useNavigate } from 'react-router-dom';
+import * as S from './NotFoundPage.styled';
+
+const NotFoundPage = () => {
+ const navigate = useNavigate();
+
+ const handleBack = () => {
+ navigate(-1);
+ };
+ return (
+
+ 뒤로 가기
+
+ );
+};
+
+export default NotFoundPage;
diff --git a/src/components/common/sidebar/editMyProfileImg/EditMyProfileImg.tsx b/src/components/common/sidebar/editMyProfileImg/EditMyProfileImg.tsx
index 387a5d1f..32b129c0 100644
--- a/src/components/common/sidebar/editMyProfileImg/EditMyProfileImg.tsx
+++ b/src/components/common/sidebar/editMyProfileImg/EditMyProfileImg.tsx
@@ -2,9 +2,12 @@ import React from 'react';
import { useUploadProfileImg } from '../../../../hooks/useMyInfo';
import * as S from './EditMyProfileImg.styled';
import { PhotoIcon } from '@heroicons/react/24/outline';
+import { useModal } from '../../../../hooks/useModal';
+import Modal from '../../modal/Modal';
const EditMyProfileImg = () => {
- const { uploadProfileImg } = useUploadProfileImg();
+ const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
+ const { uploadProfileImg } = useUploadProfileImg(handleModalOpen);
const handleFileChange = (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
@@ -24,6 +27,9 @@ const EditMyProfileImg = () => {
onChange={handleFileChange}
/>
+
+ {message}
+
);
};
diff --git a/src/components/mypage/myProfile/myProfile.tsx b/src/components/mypage/myProfile/myProfile.tsx
index 7886013d..89492d53 100644
--- a/src/components/mypage/myProfile/myProfile.tsx
+++ b/src/components/mypage/myProfile/myProfile.tsx
@@ -23,6 +23,9 @@ import { Link } from 'react-router-dom';
import BeginnerIcon from '../../../assets/beginner.svg';
import OptionBox from './optionBox';
import TextareaAutosize from 'react-textarea-autosize';
+import Modal from '../../common/modal/Modal';
+import { useModal } from '../../../hooks/useModal';
+import Spinner from '../Spinner';
const profileSchema = z.object({
nickname: z
@@ -76,7 +79,8 @@ const MyProfile = () => {
useNickNameVerification();
const { myData, isLoading } = useMyProfileInfo();
- const { editMyProfile } = useEditMyProfileInfo();
+ const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
+ const { editMyProfile } = useEditMyProfileInfo(handleModalOpen);
const {
control,
@@ -139,8 +143,9 @@ const MyProfile = () => {
};
if (isLoading) {
- return 로딩중...
;
+ return ;
}
+
if (!myData) {
return 유저 정보를 불러 올 수 없습니다.
;
}
@@ -525,6 +530,9 @@ const MyProfile = () => {
)}
+
+ {message}
+
);
};
diff --git a/src/constants/modalMessage.ts b/src/constants/modalMessage.ts
index fcb06812..af342656 100644
--- a/src/constants/modalMessage.ts
+++ b/src/constants/modalMessage.ts
@@ -2,6 +2,15 @@ export const MODAL_MESSAGE = {
pass: '지원자를 합격 리스트에 추가했습니다.',
nonPass: '지원자를 불합격 리스트에 추가했습니다.',
equalStatus: '이미 동일한 리스트에 추가하신 상태입니다.',
- signUp: '회원가입에 성공하셨습니다!',
- login: '로그인 되었습니다.',
+ signUpSuccess: '회원가입 완료되었습니다.',
+ signUpFail: '회원가입 실패하였습니다.',
+ changePasswordSuccess: '비밀번호가 성공적으로 재설정 되었습니다.',
+ changePasswordFail: '비밀번호 재설정에 실패하였습니다.',
+ loginSuccess: '로그인 되었습니다.',
+ loginFail: '가입되지 않은 계정입니다.',
+ logout: '로그아웃 되었습니다.',
+ myProfileSuccess: '프로필 수정이 완료되었습니다.',
+ myProfileFail: '프로필 수정에 실패했습니다.',
+ profileImgSuccess: '프로필 이미지가 업로드 되었습니다.',
+ profileImgFail: '이미지는 5MB 이하, .png.jpg.jpeg.svg 형식만 가능합니다.',
} as const;
diff --git a/src/constants/routes.ts b/src/constants/routes.ts
index 5d99d7b1..5110fd63 100644
--- a/src/constants/routes.ts
+++ b/src/constants/routes.ts
@@ -15,4 +15,5 @@ export const ROUTES = {
userpage: '/user',
userJoinedProject: 'join-projects',
modifyProject: '/project-modify',
+ notFound: '/not-found',
} as const;
diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts
index d9d0088d..5e92e496 100644
--- a/src/hooks/useAuth.ts
+++ b/src/hooks/useAuth.ts
@@ -4,17 +4,16 @@ import { registerFormValues } from '../pages/register/Register';
import { changePasswordFormValues } from '../pages/changePassword/ChangePassword';
import { loginFormValues } from '../pages/login/Login';
import useAuthStore from '../store/authStore';
-import { useAlert } from './useAlert';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { LoginResponse } from '../models/auth';
import { ROUTES } from '../constants/routes';
import { AxiosError } from 'axios';
import { myInfoKey } from './queries/keys';
+import { MODAL_MESSAGE } from '../constants/modalMessage';
-export const useAuth = () => {
+export const useAuth = (handleModalOpen: (message: string) => void) => {
const navigate = useNavigate();
const { storeLogin, storeLogout } = useAuthStore.getState();
- const { showAlert } = useAlert();
const queryClient = useQueryClient();
const signupMutation = useMutation<
@@ -26,11 +25,11 @@ export const useAuth = () => {
await postSignUp({ email, password, nickname });
},
onSuccess: () => {
- showAlert('회원가입 완료되었습니다.');
+ handleModalOpen(MODAL_MESSAGE.signUpSuccess);
navigate(ROUTES.login);
},
onError: () => {
- showAlert('회원가입 실패하였습니다.');
+ handleModalOpen(MODAL_MESSAGE.signUpFail);
},
});
@@ -43,11 +42,11 @@ export const useAuth = () => {
await postResetPassword({ email, newPassword });
},
onSuccess: () => {
- showAlert('비밀번호가 성공적으로 재설정 되었습니다.');
+ handleModalOpen(MODAL_MESSAGE.changePasswordSuccess);
navigate(ROUTES.login);
},
onError: () => {
- showAlert('비밀번호 재설정에 실패하였습니다.');
+ handleModalOpen(MODAL_MESSAGE.changePasswordFail);
},
});
@@ -64,12 +63,12 @@ export const useAuth = () => {
},
onSuccess: (data) => {
const { accessToken, refreshToken, userData } = data;
- showAlert('로그인 되었습니다.');
+ handleModalOpen(MODAL_MESSAGE.loginSuccess);
storeLogin(accessToken, refreshToken, userData);
navigate(ROUTES.home);
},
onError: () => {
- showAlert('가입되지 않은 계정입니다.');
+ handleModalOpen(MODAL_MESSAGE.loginFail);
},
});
@@ -92,7 +91,7 @@ export const useAuth = () => {
const userLogout = () => {
storeLogout();
queryClient.removeQueries({ queryKey: myInfoKey.myProfile });
- showAlert('로그아웃 되었습니다.');
+ handleModalOpen(MODAL_MESSAGE.logout);
navigate(ROUTES.home);
};
diff --git a/src/hooks/useMyInfo.ts b/src/hooks/useMyInfo.ts
index c4c1d545..4c2c4db3 100644
--- a/src/hooks/useMyInfo.ts
+++ b/src/hooks/useMyInfo.ts
@@ -7,7 +7,6 @@ import {
putMyInfo,
} from '../api/mypage.api';
import { EditMyInfo, UserInfo } from '../models/userInfo';
-import { useAlert } from './useAlert';
import { AxiosError } from 'axios';
import { useNavigate } from 'react-router-dom';
import { ROUTES } from '../constants/routes';
@@ -17,6 +16,7 @@ import {
MyAppliedProjectStatusList,
MyJoinedProjectList,
} from '../models/userProject';
+import { MODAL_MESSAGE } from '../constants/modalMessage';
export const useMyProfileInfo = () => {
const { isLoggedIn } = useAuthStore();
@@ -31,9 +31,10 @@ export const useMyProfileInfo = () => {
return { myData: data, isLoading };
};
-export const useEditMyProfileInfo = () => {
+export const useEditMyProfileInfo = (
+ handleModalOpen: (message: string) => void
+) => {
const navigate = useNavigate();
- const { showAlert } = useAlert();
const queryClient = useQueryClient();
const editProfileMutation = useMutation({
@@ -42,11 +43,11 @@ export const useEditMyProfileInfo = () => {
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: myInfoKey.myProfile });
- showAlert('프로필 수정이 완료되었습니다.');
+ handleModalOpen(MODAL_MESSAGE.myProfileSuccess);
navigate(ROUTES.mypage);
},
onError: () => {
- showAlert('프로필 수정에 실패했습니다.');
+ handleModalOpen(MODAL_MESSAGE.myProfileFail);
},
});
@@ -57,9 +58,10 @@ export const useEditMyProfileInfo = () => {
return { editMyProfile };
};
-export const useUploadProfileImg = () => {
+export const useUploadProfileImg = (
+ handleModalOpen: (message: string) => void
+) => {
const queryClient = useQueryClient();
- const { showAlert } = useAlert();
const uploadProfileImgMutation = useMutation({
mutationFn: async (data: File) => {
@@ -67,10 +69,10 @@ export const useUploadProfileImg = () => {
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: myInfoKey.myProfile });
- showAlert('프로필 이미지가 업로드 되었습니다.');
+ handleModalOpen(MODAL_MESSAGE.profileImgSuccess);
},
onError: () => {
- showAlert(`이미지는 5MB 이하, .png.jpg.jpeg.svg 형식만 가능합니다.`);
+ handleModalOpen(MODAL_MESSAGE.profileImgFail);
},
});
diff --git a/src/mock/auth.ts b/src/mock/auth.ts
new file mode 100644
index 00000000..f47564ac
--- /dev/null
+++ b/src/mock/auth.ts
@@ -0,0 +1,10 @@
+import { http, HttpResponse } from 'msw';
+
+export const login = http.post(
+ `${import.meta.env.VITE_API_BASE_URL}/auth/login`,
+ () => {
+ return HttpResponse.json({
+ status: 200,
+ });
+ }
+);
diff --git a/src/mock/browser.ts b/src/mock/browser.ts
index 555a74fe..6febaff4 100644
--- a/src/mock/browser.ts
+++ b/src/mock/browser.ts
@@ -9,12 +9,14 @@ import {
import { projectDetail } from './projectDetail';
import {
myPageAppliedProjectList,
+ mypageEditProfile,
myPageJoinedProjectList,
myPagePositionTag,
myPageProfile,
myPageSkillTag,
} from './mypage';
import { userPageAppliedProjectList, userPageProfile } from './userpage';
+import { login } from './auth';
import { fetchProjectLists, fetchProjectStatistic } from './projectLists';
import {
fetchMethodTag,
@@ -41,6 +43,9 @@ export const handlers = [
myPageAppliedProjectList,
userPageProfile,
userPageAppliedProjectList,
+ passNonPassList,
+ mypageEditProfile,
+ login,
];
export const worker = setupWorker(...handlers);
diff --git a/src/mock/mypage.ts b/src/mock/mypage.ts
index 2b48d4fd..74c04ed0 100644
--- a/src/mock/mypage.ts
+++ b/src/mock/mypage.ts
@@ -49,3 +49,12 @@ export const myPageAppliedProjectList = http.get(
});
}
);
+
+export const mypageEditProfile = http.put(
+ `${import.meta.env.VITE_API_BASE_URL}/user/me`,
+ () => {
+ return HttpResponse.json({
+ status: 200,
+ });
+ }
+);
diff --git a/src/pages/changePassword/ChangePassword.tsx b/src/pages/changePassword/ChangePassword.tsx
index 574a0dbd..9595ada6 100644
--- a/src/pages/changePassword/ChangePassword.tsx
+++ b/src/pages/changePassword/ChangePassword.tsx
@@ -13,6 +13,8 @@ import Button from '../../components/common/Button/Button';
import { ERROR_MESSAGES } from '../../constants/authConstants';
import { useAuth } from '../../hooks/useAuth';
import { ROUTES } from '../../constants/routes';
+import { useModal } from '../../hooks/useModal';
+import Modal from '../../components/common/modal/Modal';
const changePasswordSchema = z
.object({
@@ -38,7 +40,8 @@ const changePasswordSchema = z
export type changePasswordFormValues = z.infer;
const ChangePassword = () => {
- const { resetPassword } = useAuth();
+ const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
+ const { resetPassword } = useAuth(handleModalOpen);
const {
emailMessage,
@@ -234,6 +237,9 @@ const ChangePassword = () => {
+
+ {message}
+
);
};
diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx
index c3a5ee46..e942cf75 100644
--- a/src/pages/login/Login.tsx
+++ b/src/pages/login/Login.tsx
@@ -11,6 +11,8 @@ import Button from '../../components/common/Button/Button';
import { ERROR_MESSAGES } from '../../constants/authConstants';
import { useAuth } from '../../hooks/useAuth';
import { ROUTES } from '../../constants/routes';
+import { useModal } from '../../hooks/useModal';
+import Modal from '../../components/common/modal/Modal';
const loginSchema = z.object({
email: z
@@ -23,7 +25,9 @@ const loginSchema = z.object({
export type loginFormValues = z.infer;
const Login = () => {
- const { userLogin } = useAuth();
+ const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
+ const { userLogin } = useAuth(handleModalOpen);
+
const {
control,
handleSubmit,
@@ -109,6 +113,9 @@ const Login = () => {
+
+ {message}
+
);
};
diff --git a/src/pages/register/Register.tsx b/src/pages/register/Register.tsx
index 8958a8ad..8c0eeb9b 100644
--- a/src/pages/register/Register.tsx
+++ b/src/pages/register/Register.tsx
@@ -18,6 +18,8 @@ import { ERROR_MESSAGES } from '../../constants/authConstants';
import { useAuth } from '../../hooks/useAuth';
import useNickNameVerification from '../../hooks/useNicknameVerification';
import { ROUTES } from '../../constants/routes';
+import { useModal } from '../../hooks/useModal';
+import Modal from '../../components/common/modal/Modal';
const registerSchema = z
.object({
@@ -49,7 +51,8 @@ const registerSchema = z
export type registerFormValues = z.infer;
const Register = () => {
- const { userSignup } = useAuth();
+ const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
+ const { userSignup } = useAuth(handleModalOpen);
const {
control,
@@ -294,6 +297,9 @@ const Register = () => {
+
+ {message}
+
);
};