diff --git a/src/api/memberApi.js b/src/api/memberApi.js index aa92759..7939d97 100644 --- a/src/api/memberApi.js +++ b/src/api/memberApi.js @@ -10,8 +10,20 @@ export const onJoin = joinForm => { return requestForAll.post(memberApi, joinForm); }; -export const onCheckNicknameDuplication = nickname => { - return requestForAll.get(memberApi + '/nickname', { params: { nickname } }); +export const onCheckNicknameDuplication = async nickname => { + return await requestForAll.get(memberApi + '/nickname', { params: { nickname } }); +}; + +export const onNicknameChange = updateNicknameRequest => { + return requestForAuth.put(memberApi + '/page/me', updateNicknameRequest); +}; + +export const onMyPage = async () => { + return await requestForAuth.get(memberApi + '/page/me'); +}; + +export const onMembershipWithdrawal = async () => { + return await requestForAuth.delete(memberApi); }; export const onChangePasswordWithVerifiedEmailByNoAuthUser = (email, password) => { diff --git a/src/components/header/HamburgerSideBar.js b/src/components/header/HamburgerSideBar.js index e2bfda3..8aa3cae 100644 --- a/src/components/header/HamburgerSideBar.js +++ b/src/components/header/HamburgerSideBar.js @@ -63,6 +63,15 @@ const HamburgerSideBar = ({ onCancle, onLogout, visible }) => { ) : ( <> + { + handlePageButtonClick('/mypage'); + onCancle(); + }} + > + 마이페이지 + + { diff --git a/src/components/header/HeaderBar.js b/src/components/header/HeaderBar.js index 3eda5a4..a1b9cf6 100644 --- a/src/components/header/HeaderBar.js +++ b/src/components/header/HeaderBar.js @@ -97,6 +97,13 @@ const HeaderBar = () => { ) : ( + + 마이페이지 + { + return ( + + ); +}; + +Calendar.defaultProps = { + size: IconSize.normal, + lineColor: Colors.formIconFirst, + fillColor: '', +}; + +export default Calendar; diff --git a/src/pages/MyPage.js b/src/pages/MyPage.js index f94567b..78102d5 100644 --- a/src/pages/MyPage.js +++ b/src/pages/MyPage.js @@ -1,9 +1,332 @@ +import * as Yup from 'yup'; + +import { Colors, FontSize, Shadows } from '../styles'; +import { Columns, Container, Hero } from 'react-bulma-components'; +import { + onCheckNicknameDuplication, + onMembershipWithdrawal, + onMyPage, + onNicknameChange, +} from '../api/memberApi'; +import { useEffect, useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; + +import AnimatedIcon from '../components/icons/AnimatedIcon'; +import Buttons from '../components/common/Buttons'; +import IconInput from '../components/common/IconInput'; +import Swal from 'sweetalert2'; +import styled from '@emotion/styled'; +import { useFormik } from 'formik'; +import { useNavigate } from 'react-router-dom'; + const MyPage = () => { + const USER_QUERY_KEY = 'userData'; + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [validNickname, setValidNickname] = useState(false); + const [validNicknameHistory, setValidNicknameHistory] = useState(undefined); + const [nicknameDisabled, setNicknameDisabled] = useState(true); + const [user, setUser] = useState({}); + + const initialValues = { + nickname: '', + }; + + const validationSchema = Yup.object().shape({ + nickname: Yup.string() + .strict(true) + .required('닉네임을 입력해주세요') + .matches(/^[a-zA-Z0-9가-힣_]{2,10}$/, '2~10 글자의 문자를 입력해주세요'), + }); + + const { errors, handleBlur, handleSubmit, handleChange, touched, values } = useFormik({ + initialValues, + validationSchema, + onSubmit: async (values, formikHelper) => { + if (!validNickname) { + Swal.fire({ + icon: 'error', + title: '닉네임 중복 확인을 해주세요.', + }); + return; + } + formikHelper.resetForm(); + formikHelper.setStatus({ success: true }); + formikHelper.setSubmitting(false); + + nicknameModifyMutation.mutate(values.nickname); + }, + }); + + const handleNicknameChangeSubmit = event => { + handleSubmit(); + event.preventDefault(); + }; + + const handleCheckDuplicatedNameButton = async nickname => { + if (errors.nickname) { + return; + } + + const result = await onCheckNicknameDuplication(nickname); + + if (result.data && validNicknameHistory !== nickname) { + const usable = result.data.usable; + if (usable) { + setValidNickname(true); + setValidNicknameHistory(nickname); + } + Swal.fire({ + icon: usable ? 'success' : 'warning', + text: usable ? `'${nickname}' 사용 가능!` : '이미 존재하는 닉네임 입니다.', + }); + } + }; + + const handleNicknameInputChange = event => { + validNicknameHistory && event.target.value === validNicknameHistory + ? setValidNickname(true) + : setValidNickname(false); + handleChange(event); + }; + + const resolveRightIconComponent = val => { + return ( + touched[val] && + (!errors[val] ? ( + + ) : ( + + )) + ); + }; + + const handleMembershipWithdrawalClick = () => { + Swal.fire({ + icon: 'question', + text: '정말 탈퇴를 진행하실건가요?', + showCancelButton: true, + confirmButtonColor: `${Colors.successFirst}`, + cancelButtonColor: `${Colors.warningFirst}`, + confirmButtonText: '네', + cancelButtonText: '아니요', + }).then(result => { + if (result.isConfirmed) { + membershipWithdrawal.mutate(); + } + }); + }; + + const membershipWithdrawal = useMutation(() => onMembershipWithdrawal(), { + onMutate: async () => { + await queryClient.cancelQueries(USER_QUERY_KEY); + const previousValue = queryClient.getQueryData(USER_QUERY_KEY); + if (previousValue) { + queryClient.setQueryData(USER_QUERY_KEY, { email: user.email, nickname: user.email }); + } + return { previousValue }; + }, + onSuccess: async () => { + Swal.fire({ + icon: 'success', + text: '회원 탈퇴가 완료되었습니다.', + }).then(() => { + navigate('/'); + }); + }, + onError: (error, variables, context) => { + Swal.fire({ + icon: 'error', + text: `어딜 나가시려고`, + }).then(() => { + queryClient.setQueryData(USER_QUERY_KEY, context.previousValue); + }); + }, + }); + + const nicknameModifyMutation = useMutation(inputNickname => onNicknameChange(inputNickname), { + onMutate: async inputNickname => { + await queryClient.cancelQueries(USER_QUERY_KEY); + const previousValue = queryClient.getQueryData(USER_QUERY_KEY); + if (previousValue) { + queryClient.setQueryData(USER_QUERY_KEY, { nickname: inputNickname }); + } + return { previousValue }; + }, + onSuccess: () => { + Swal.fire({ + icon: 'success', + text: '정보가 수정되었습니다.', + }); + setNicknameDisabled(true); + }, + onError: (error, variables, context) => { + Swal.fire({ + icon: 'error', + text: `정보 수정 실패: ${error.details}`, + }).then(() => { + queryClient.setQueryData(USER_QUERY_KEY, context.previousValue); + }); + }, + }); + + const { data, status, error } = useQuery(USER_QUERY_KEY, () => + onMyPage().then(response => response.data), + ); + + useEffect(() => { + if (status === 'success') { + setUser(data); + } + }, [data]); + + if (status === 'loading') { + return Loading...; + } + + if (status === 'error') { + return Error: {error.message}; + } + return ( <> -

마이 페이지입니다.

+ + + + + { + handleNicknameChangeSubmit(event); + }} + > +

My Page

+ + + + + + + } + rightIconComponent={resolveRightIconComponent('calendar')} + disabled={true} + /> + + + + + + + + } + rightIconComponent={resolveRightIconComponent('email')} + disabled={true} + /> + + + + + + + + } + rightIconComponent={resolveRightIconComponent('nickname')} + disabled={nicknameDisabled} + /> + + + + nicknameDisabled == true + ? setNicknameDisabled(false) + : handleCheckDuplicatedNameButton(values.nickname) + } + > + {nicknameDisabled == true ? '변경하기' : '중복확인'} + + +

+      + {touched.nickname && + (errors.nickname || + (validNickname ? '닉네임 검증 완료!' : '닉네임 입력이 확인되었어요'))} +

+
+ {nicknameDisabled == false ? ( + + + 수정완료 + + + ) : ( + + )} + + + +

 

+ + 변경하기 +
+ +

 

+ + + 회원 탈퇴 + +
+
+
+
+
+
+
); }; +const StyledHeroBody = styled(Hero.Body)``; + +const StyledForm = styled.form` + width: 100%; +`; + +const DevideLine = styled.hr` + background-color: ${props => (!props.color ? Colors.mainFirst : props.color)}; + opacity: 0.6; + box-shadow: ${props => (!props.color ? Shadows.button : 'none')}; + margin-top: ${props => (!props.space ? Shadows.huge : FontSize[props.space])}; + margin-bottom: ${props => (!props.space ? FontSize.huge : FontSize[props.space])}; +`; + export default MyPage;