diff --git a/src/apis/mypage.ts b/src/apis/mypage.ts index bafd31f..cbf3a57 100644 --- a/src/apis/mypage.ts +++ b/src/apis/mypage.ts @@ -16,13 +16,13 @@ export const sendEmailVerification = async (email: string) => { }); }; -export const fetchMy = async () => { +export const fetchMyPageWithSSR = async () => { const res = await authAPI({ method: 'get', url: '/api/member/mypage', }); - return res; + return res.data; }; export const fetchProfile = async () => { diff --git a/src/constants/queryManagement.ts b/src/constants/queryManagement.ts new file mode 100644 index 0000000..6b87398 --- /dev/null +++ b/src/constants/queryManagement.ts @@ -0,0 +1,17 @@ +import { fetchMyPageWithSSR, sendEmailVerification } from '@apis/mypage'; + +const QUERY_MANAGEMENT = { + mypage: { + queryKey: 'mypage', + queryFn: fetchMyPageWithSSR, + }, +}; + +const MUTATION_MANAGEMENT = { + email: { + mutateKey: 'sendEmail', + mutateFn: (email: string) => sendEmailVerification(email), + }, +}; + +export { QUERY_MANAGEMENT, MUTATION_MANAGEMENT }; diff --git a/src/hooks/mutation/useSendEmailMutation.ts b/src/hooks/mutation/useSendEmailMutation.ts new file mode 100644 index 0000000..a444843 --- /dev/null +++ b/src/hooks/mutation/useSendEmailMutation.ts @@ -0,0 +1,22 @@ +import { MUTATION_MANAGEMENT } from '@constants/queryManagement'; +import useInput from '@hooks/useInput'; +import { useMutation } from '@tanstack/react-query'; +import { useState } from 'react'; + +const useSendEmailMutation = () => { + const [email, setEmail, resetEmail] = useInput(''); + const [isSendVerifyEmail, setIsSendVerifyEmail] = useState(false); + + const { mutate } = useMutation({ + mutationKey: [MUTATION_MANAGEMENT.email.mutateKey], + mutationFn: () => MUTATION_MANAGEMENT.email.mutateFn(email), + onMutate: () => { + alert('이메일을 보냈습니다.'); + setIsSendVerifyEmail(true); + }, + }); + + return { email, setEmail, resetEmail, isSendVerifyEmail, mutate }; +}; + +export default useSendEmailMutation; diff --git a/src/hooks/query/useMyPageQuery.ts b/src/hooks/query/useMyPageQuery.ts new file mode 100644 index 0000000..1cd29a6 --- /dev/null +++ b/src/hooks/query/useMyPageQuery.ts @@ -0,0 +1,13 @@ +import { QUERY_MANAGEMENT } from '@constants/queryManagement'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +const useMyPageQuery = () => { + const { data, refetch } = useSuspenseQuery({ + queryKey: [QUERY_MANAGEMENT.mypage.queryKey], + queryFn: QUERY_MANAGEMENT.mypage.queryFn, + }); + + return { data, refetch }; +}; + +export default useMyPageQuery; diff --git a/src/hooks/useInput.ts b/src/hooks/useInput.ts new file mode 100644 index 0000000..448be43 --- /dev/null +++ b/src/hooks/useInput.ts @@ -0,0 +1,22 @@ +import { useState, useCallback } from 'react'; + +const useInput = (initialValue: string) => { + const [inputValue, setInputValue] = useState(initialValue); + + const handleValue = useCallback( + (e: React.ChangeEvent) => { + const { value } = e.target; + + setInputValue(value); + }, + [], + ); + + const resetValue = useCallback(() => { + setInputValue(''); + }, []); + + return [inputValue, handleValue, resetValue] as const; +}; + +export default useInput; diff --git a/src/pages/mypage.tsx b/src/pages/mypage.tsx index 94eab1f..25bf143 100644 --- a/src/pages/mypage.tsx +++ b/src/pages/mypage.tsx @@ -1,56 +1,16 @@ -import { GetServerSidePropsContext } from 'next'; -import { ChangeEvent, useState } from 'react'; import styled from '@emotion/styled'; import Image from 'next/image'; -import { parse } from 'cookie'; -import axios from 'axios'; -import checkEmailAddressValidation from 'src/utils/checkEmailAddressValidation'; -import { SERVER_URL } from '@config/index'; -import { MyPage } from '@type/mypage'; import Icon from '@components/Icon'; -import { fetchMy, sendEmailVerification } from '@apis/mypage'; - -interface Props { - data: MyPage; -} - -const Mypage = (props: Props) => { - const [info, setInfo] = useState(props.data); - - const [email, setEmail] = useState(''); - const [sendEmail, setSendEmail] = useState(false); - - const handleEmail = (e: ChangeEvent) => { - setEmail(e.target.value); - }; - - const sendVerifyEmail = async () => { - if (!checkEmailAddressValidation(email)) { - alert('유효한 이메일 주소가 아닙니다! 다시 입력해주세요'); - return; - } - - try { - await sendEmailVerification(email); - setSendEmail(true); - - alert( - '이메일 전송이 완료되었습니다.\n입력하신 이메일의 링크를 클릭하신 후 동기화 버튼을 눌러주세요!', - ); - } catch (error) { - console.log(error); - } - }; - - const refreshMyInfo = async () => { - try { - const res = await fetchMy(); - setInfo(res.data); - } catch (error) { - console.log(error); - } - }; +import withAuthServerSideProps from 'src/utils/withAuthentication'; +import { QueryClient, dehydrate } from '@tanstack/react-query'; +import { QUERY_MANAGEMENT } from '@constants/queryManagement'; +import useSendEmailMutation from '@hooks/mutation/useSendEmailMutation'; +import useMyPageQuery from '@hooks/query/useMyPageQuery'; + +const Mypage = () => { + const { data, refetch } = useMyPageQuery(); + const { email, setEmail, resetEmail, isSendVerifyEmail, mutate } = useSendEmailMutation(); return ( @@ -60,23 +20,23 @@ const Mypage = (props: Props) => { 프로필 - + 이름 - {info.nickName} + {data.nickName} 이메일 - {info.userEmailVerified ? ( - + {data.userEmailVerified ? ( + ) : ( <> - - + + mutate()} disabled={isSendVerifyEmail}> @@ -87,13 +47,13 @@ const Mypage = (props: Props) => { 이메일 인증 - {info.userEmailVerified ? '인증되었습니다.' : '미인증 상태입니다.'} + {data.userEmailVerified ? '인증되었습니다.' : '미인증 상태입니다.'} 정보 동기화 - + refetch()}> @@ -104,38 +64,6 @@ const Mypage = (props: Props) => { ); }; -export default Mypage; - -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - try { - const cookies = parse(context.req.headers.cookie || 'no-cookie'); - - const accessToken = cookies.accessToken || undefined; - - const res = await axios({ - method: 'get', - url: SERVER_URL + '/api/member/mypage', - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - - return { - props: { - data: res.data, - }, - }; - } catch (error) { - console.log(error); - return { - redirect: { - permanent: false, - destination: '/login', - }, - }; - } -}; - const Container = styled.div` margin: 0 auto; color: #020202; @@ -207,3 +135,30 @@ const EmailSendBtn = styled.button` const ProfileImage = styled(Image)` border-radius: 50%; `; + +export default Mypage; + +const fetchMyPage = async () => { + const queryClient = new QueryClient(); + + try { + await queryClient.prefetchQuery({ + queryKey: [QUERY_MANAGEMENT.mypage.queryKey], + queryFn: QUERY_MANAGEMENT.mypage.queryFn, + }); + + return { + props: { + dehydratedState: dehydrate(queryClient), + }, + }; + } catch (error) { + return { + redirect: { + destination: '/', + }, + }; + } +}; + +export const getServerSideProps = withAuthServerSideProps(fetchMyPage); diff --git a/src/utils/withAuthentication.tsx b/src/utils/withAuthentication.tsx new file mode 100644 index 0000000..61e23c0 --- /dev/null +++ b/src/utils/withAuthentication.tsx @@ -0,0 +1,25 @@ +import authAPI from '@apis/authAPI'; +import { parse } from 'cookie'; +import { GetServerSidePropsContext } from 'next'; + +const withAuthServerSideProps = (getServerSidePropsFunction: () => Promise) => { + return async (context: GetServerSidePropsContext) => { + const cookies = context.req.headers.cookie || ''; + + const accessToken = parse(cookies).accessToken; + + if (!accessToken) { + throw new Error('401 Unauthorized'); + } + + authAPI.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`; + + const res = await getServerSidePropsFunction(); + + authAPI.defaults.headers.common['Authorization'] = ''; + + return res; + }; +}; + +export default withAuthServerSideProps;