Skip to content
1,699 changes: 1,699 additions & 0 deletions src/assets/notFoundImg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion src/components/common/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -64,6 +67,9 @@ function Header() {
</>
</DropDown>
</nav>
<Modal isOpen={isOpen} onClose={handleModalClose}>
{message}
</Modal>
</S.HeaderContainer>
);
}
Expand Down
29 changes: 23 additions & 6 deletions src/components/common/modal/Modal.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
`;
5 changes: 4 additions & 1 deletion src/components/common/modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -43,6 +43,9 @@ const Modal = ({ children, isOpen, onClose }: ModalProps) => {
<S.ModalCloseButton onClick={handleClose}>
<XMarkIcon />
</S.ModalCloseButton>
<S.ModalIconWrapper>
<CheckCircleIcon />
</S.ModalIconWrapper>
<S.ModalContents>{children}</S.ModalContents>
</S.ModalBody>
</S.ModalContainer>
Expand Down
34 changes: 34 additions & 0 deletions src/components/common/page/notFoundPage/NotFoundPage.styled.ts
Original file line number Diff line number Diff line change
@@ -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;
}
`;
17 changes: 17 additions & 0 deletions src/components/common/page/notFoundPage/NotFoundPage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<S.Container>
<S.BackButton onClick={handleBack}>뒤로 가기</S.BackButton>
</S.Container>
);
};

export default NotFoundPage;
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLInputElement>) => {
const file = e.target.files?.[0];
Expand All @@ -24,6 +27,9 @@ const EditMyProfileImg = () => {
onChange={handleFileChange}
/>
</label>
<Modal isOpen={isOpen} onClose={handleModalClose}>
{message}
</Modal>
</S.Container>
);
};
Expand Down
12 changes: 10 additions & 2 deletions src/components/mypage/myProfile/myProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -139,8 +143,9 @@ const MyProfile = () => {
};

if (isLoading) {
return <div>로딩중...</div>;
return <Spinner size='50px' color='#3e5879;' />;
}

if (!myData) {
return <div>유저 정보를 불러 올 수 없습니다.</div>;
}
Expand Down Expand Up @@ -525,6 +530,9 @@ const MyProfile = () => {
</form>
)}
</S.Container>
<Modal isOpen={isOpen} onClose={handleModalClose}>
{message}
</Modal>
</S.Box>
);
};
Expand Down
13 changes: 11 additions & 2 deletions src/constants/modalMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
1 change: 1 addition & 0 deletions src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const ROUTES = {
userpage: '/user',
userJoinedProject: 'join-projects',
modifyProject: '/project-modify',
notFound: '/not-found',
} as const;
19 changes: 9 additions & 10 deletions src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand All @@ -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);
},
});

Expand All @@ -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);
},
});

Expand All @@ -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);
},
});

Expand All @@ -92,7 +91,7 @@ export const useAuth = () => {
const userLogout = () => {
storeLogout();
queryClient.removeQueries({ queryKey: myInfoKey.myProfile });
showAlert('로그아웃 되었습니다.');
handleModalOpen(MODAL_MESSAGE.logout);
navigate(ROUTES.home);
};

Expand Down
20 changes: 11 additions & 9 deletions src/hooks/useMyInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,6 +16,7 @@ import {
MyAppliedProjectStatusList,
MyJoinedProjectList,
} from '../models/userProject';
import { MODAL_MESSAGE } from '../constants/modalMessage';

export const useMyProfileInfo = () => {
const { isLoggedIn } = useAuthStore();
Expand All @@ -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<void, AxiosError, EditMyInfo>({
Expand All @@ -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);
},
});

Expand All @@ -57,20 +58,21 @@ export const useEditMyProfileInfo = () => {
return { editMyProfile };
};

export const useUploadProfileImg = () => {
export const useUploadProfileImg = (
handleModalOpen: (message: string) => void
) => {
const queryClient = useQueryClient();
const { showAlert } = useAlert();

const uploadProfileImgMutation = useMutation<void, AxiosError, File>({
mutationFn: async (data: File) => {
await patchMyProfileImg(data);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: myInfoKey.myProfile });
showAlert('프로필 이미지가 업로드 되었습니다.');
handleModalOpen(MODAL_MESSAGE.profileImgSuccess);
},
onError: () => {
showAlert(`이미지는 5MB 이하, .png.jpg.jpeg.svg 형식만 가능합니다.`);
handleModalOpen(MODAL_MESSAGE.profileImgFail);
},
});

Expand Down
10 changes: 10 additions & 0 deletions src/mock/auth.ts
Original file line number Diff line number Diff line change
@@ -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,
});
}
);
Loading