Skip to content

Commit

Permalink
Merge pull request #279 from TheUpperPart/Feat/#274
Browse files Browse the repository at this point in the history
Feat/#274 SSR에서 토큰 탑재 자동화 및 마이페이지 리팩토링
  • Loading branch information
navyjeongs authored Jan 25, 2024
2 parents b4e6d97 + 7c613ab commit 7629f0c
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 91 deletions.
4 changes: 2 additions & 2 deletions src/apis/mypage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export const sendEmailVerification = async (email: string) => {
});
};

export const fetchMy = async () => {
export const fetchMyPageWithSSR = async () => {
const res = await authAPI<MyPage>({
method: 'get',
url: '/api/member/mypage',
});

return res;
return res.data;
};

export const fetchProfile = async () => {
Expand Down
17 changes: 17 additions & 0 deletions src/constants/queryManagement.ts
Original file line number Diff line number Diff line change
@@ -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 };
22 changes: 22 additions & 0 deletions src/hooks/mutation/useSendEmailMutation.ts
Original file line number Diff line number Diff line change
@@ -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<boolean>(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;
13 changes: 13 additions & 0 deletions src/hooks/query/useMyPageQuery.ts
Original file line number Diff line number Diff line change
@@ -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;
22 changes: 22 additions & 0 deletions src/hooks/useInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useState, useCallback } from 'react';

const useInput = (initialValue: string) => {
const [inputValue, setInputValue] = useState<string>(initialValue);

const handleValue = useCallback(
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { value } = e.target;

setInputValue(value);
},
[],
);

const resetValue = useCallback(() => {
setInputValue('');
}, []);

return [inputValue, handleValue, resetValue] as const;
};

export default useInput;
133 changes: 44 additions & 89 deletions src/pages/mypage.tsx
Original file line number Diff line number Diff line change
@@ -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<MyPage>(props.data);

const [email, setEmail] = useState<string>('');
const [sendEmail, setSendEmail] = useState<boolean>(false);

const handleEmail = (e: ChangeEvent<HTMLInputElement>) => {
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 (
<Container>
Expand All @@ -60,23 +20,23 @@ const Mypage = (props: Props) => {
<Content>
<GridLabel>프로필</GridLabel>
<GridValue>
<ProfileImage src={info.profileImageUrl} alt='profile' width='50' height='50' />
<ProfileImage src={data.profileImageUrl} alt='profile' width='50' height='50' />
</GridValue>
</Content>
<Content>
<GridLabel>이름</GridLabel>
<GridValue>{info.nickName}</GridValue>
<GridValue>{data.nickName}</GridValue>
</Content>
<Content>
<GridLabel>이메일</GridLabel>
<GridValue>
<EmailForm>
{info.userEmailVerified ? (
<EmailInput disabled value={info.email} />
{data.userEmailVerified ? (
<EmailInput disabled value={data.email} />
) : (
<>
<EmailInput value={email} onChange={handleEmail} disabled={sendEmail} />
<EmailSendBtn onClick={sendVerifyEmail} disabled={sendEmail}>
<EmailInput value={email} onChange={setEmail} disabled={isSendVerifyEmail} />
<EmailSendBtn onClick={() => mutate()} disabled={isSendVerifyEmail}>
<Icon kind='sendEmail' size='28' />
</EmailSendBtn>
</>
Expand All @@ -87,13 +47,13 @@ const Mypage = (props: Props) => {
<Content>
<GridLabel>이메일 인증</GridLabel>
<GridValue>
{info.userEmailVerified ? '인증되었습니다.' : '미인증 상태입니다.'}
{data.userEmailVerified ? '인증되었습니다.' : '미인증 상태입니다.'}
</GridValue>
</Content>
<Content>
<GridLabel>정보 동기화</GridLabel>
<GridValue>
<EmailSendBtn onClick={refreshMyInfo}>
<EmailSendBtn onClick={() => refetch()}>
<Icon kind='refresh' size='28' />
</EmailSendBtn>
</GridValue>
Expand All @@ -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<MyPage>({
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;
Expand Down Expand Up @@ -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);
25 changes: 25 additions & 0 deletions src/utils/withAuthentication.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import authAPI from '@apis/authAPI';
import { parse } from 'cookie';
import { GetServerSidePropsContext } from 'next';

const withAuthServerSideProps = (getServerSidePropsFunction: () => Promise<any>) => {
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;

0 comments on commit 7629f0c

Please sign in to comment.