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} + ); };